diff options
Diffstat (limited to 'alc')
108 files changed, 15060 insertions, 20177 deletions
diff --git a/alc/alc.cpp b/alc/alc.cpp index bee42fc5..af8ff55d 100644 --- a/alc/alc.cpp +++ b/alc/alc.cpp @@ -22,10 +22,16 @@ #include "version.h" -#include <exception> +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + #include <algorithm> #include <array> #include <atomic> +#include <bitset> +#include <cassert> #include <cctype> #include <chrono> #include <cinttypes> @@ -42,9 +48,10 @@ #include <memory> #include <mutex> #include <new> -#include <numeric> +#include <stddef.h> +#include <stdexcept> #include <string> -#include <thread> +#include <type_traits> #include <utility> #include "AL/al.h" @@ -53,50 +60,55 @@ #include "AL/efx.h" #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 "alcmain.h" +#include "albit.h" #include "albyte.h" #include "alconfig.h" -#include "alcontext.h" -#include "alexcpt.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" -#include "ambidefs.h" #include "atomic.h" -#include "bformatdec.h" -#include "bs2b.h" -#include "compat.h" -#include "cpu_caps.h" -#include "devformat.h" +#include "context.h" +#include "core/ambidefs.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/context.h" +#include "core/cpu_caps.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/except.h" +#include "core/helpers.h" +#include "core/mastering.h" +#include "core/mixer/hrtfdefs.h" +#include "core/fpu_ctrl.h" +#include "core/front_stablizer.h" +#include "core/logging.h" +#include "core/uhjfilter.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "device.h" #include "effects/base.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "fpu_modes.h" -#include "hrtf.h" #include "inprogext.h" #include "intrusive_ptr.h" -#include "logging.h" -#include "mastering.h" #include "opthelpers.h" -#include "pragmadefs.h" -#include "ringbuffer.h" #include "strutils.h" #include "threads.h" -#include "uhjfilter.h" -#include "vecmat.h" #include "vector.h" #include "backends/base.h" #include "backends/null.h" #include "backends/loopback.h" +#ifdef HAVE_PIPEWIRE +#include "backends/pipewire.h" +#endif #ifdef HAVE_JACK #include "backends/jack.h" #endif @@ -115,6 +127,9 @@ #ifdef HAVE_OPENSL #include "backends/opensl.h" #endif +#ifdef HAVE_OBOE +#include "backends/oboe.h" +#endif #ifdef HAVE_SOLARIS #include "backends/solaris.h" #endif @@ -124,9 +139,6 @@ #ifdef HAVE_OSS #include "backends/oss.h" #endif -#ifdef HAVE_QSA -#include "backends/qsa.h" -#endif #ifdef HAVE_DSOUND #include "backends/dsound.h" #endif @@ -143,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<WCHAR*>(module), &module); + break; + } + return TRUE; +} +#endif namespace { @@ -150,6 +192,9 @@ using namespace std::placeholders; using std::chrono::seconds; using std::chrono::nanoseconds; +using voidp = void*; +using float2 = std::array<float,2>; + /************************************************ * Backends @@ -160,24 +205,27 @@ struct BackendInfo { }; BackendInfo BackendList[] = { -#ifdef HAVE_JACK - { "jack", JackBackendFactory::getFactory }, +#ifdef HAVE_PIPEWIRE + { "pipewire", PipeWireBackendFactory::getFactory }, #endif #ifdef HAVE_PULSEAUDIO { "pulse", PulseBackendFactory::getFactory }, #endif -#ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, -#endif #ifdef HAVE_WASAPI { "wasapi", WasapiBackendFactory::getFactory }, #endif #ifdef HAVE_COREAUDIO { "core", CoreAudioBackendFactory::getFactory }, #endif +#ifdef HAVE_OBOE + { "oboe", OboeBackendFactory::getFactory }, +#endif #ifdef HAVE_OPENSL { "opensl", OSLBackendFactory::getFactory }, #endif +#ifdef HAVE_ALSA + { "alsa", AlsaBackendFactory::getFactory }, +#endif #ifdef HAVE_SOLARIS { "solaris", SolarisBackendFactory::getFactory }, #endif @@ -187,8 +235,8 @@ BackendInfo BackendList[] = { #ifdef HAVE_OSS { "oss", OSSBackendFactory::getFactory }, #endif -#ifdef HAVE_QSA - { "qsa", QSABackendFactory::getFactory }, +#ifdef HAVE_JACK + { "jack", JackBackendFactory::getFactory }, #endif #ifdef HAVE_DSOUND { "dsound", DSoundBackendFactory::getFactory }, @@ -208,7 +256,6 @@ BackendInfo BackendList[] = { { "wave", WaveBackendFactory::getFactory }, #endif }; -auto BackendListEnd = std::end(BackendList); BackendFactory *PlaybackFactory{}; BackendFactory *CaptureFactory{}; @@ -219,8 +266,8 @@ BackendFactory *CaptureFactory{}; ************************************************/ #define DECL(x) { #x, reinterpret_cast<void*>(x) } const struct { - const ALCchar *funcName; - ALCvoid *address; + const char *funcName; + void *address; } alcFunctions[] = { DECL(alcCreateContext), DECL(alcMakeContextCurrent), @@ -258,6 +305,8 @@ const struct { DECL(alcGetInteger64vSOFT), + DECL(alcReopenDeviceSOFT), + DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), @@ -393,6 +442,30 @@ const struct { DECL(alEventCallbackSOFT), DECL(alGetPointerSOFT), DECL(alGetPointervSOFT), + + DECL(alBufferCallbackSOFT), + DECL(alGetBufferPtrSOFT), + DECL(alGetBuffer3PtrSOFT), + DECL(alGetBufferPtrvSOFT), + + DECL(alAuxiliaryEffectSlotPlaySOFT), + DECL(alAuxiliaryEffectSlotPlayvSOFT), + DECL(alAuxiliaryEffectSlotStopSOFT), + DECL(alAuxiliaryEffectSlotStopvSOFT), + + DECL(alSourcePlayAtTimeSOFT), + DECL(alSourcePlayAtTimevSOFT), + + DECL(alBufferSubDataSOFT), + + DECL(alBufferDataStatic), +#ifdef ALSOFT_EAX +}, eaxFunctions[] = { + DECL(EAXGet), + DECL(EAXSet), + DECL(EAXGetBufferMode), + DECL(EAXSetBufferMode), +#endif }; #undef DECL @@ -470,6 +543,21 @@ constexpr struct { DECL(ALC_OUTPUT_LIMITER_SOFT), + DECL(ALC_DEVICE_CLOCK_SOFT), + DECL(ALC_DEVICE_LATENCY_SOFT), + DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), + DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), + DECL(AL_SEC_OFFSET_CLOCK_SOFT), + + DECL(ALC_OUTPUT_MODE_SOFT), + DECL(ALC_ANY_SOFT), + DECL(ALC_STEREO_BASIC_SOFT), + 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), @@ -588,6 +676,9 @@ constexpr struct { DECL(AL_SOURCE_RADIUS), + DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT), + DECL(AL_SEC_OFFSET_LATENCY_SOFT), + DECL(AL_STEREO_ANGLES), DECL(AL_UNUSED), @@ -762,6 +853,8 @@ constexpr struct { DECL(AL_VOCAL_MORPHER_WAVEFORM), DECL(AL_VOCAL_MORPHER_RATE), + DECL(AL_EFFECTSLOT_TARGET_SOFT), + DECL(AL_NUM_RESAMPLERS_SOFT), DECL(AL_DEFAULT_RESAMPLER_SOFT), DECL(AL_SOURCE_RESAMPLER_SOFT), @@ -779,9 +872,59 @@ constexpr struct { DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), - DECL(AL_EVENT_TYPE_ERROR_SOFT), - DECL(AL_EVENT_TYPE_PERFORMANCE_SOFT), - DECL(AL_EVENT_TYPE_DEPRECATED_SOFT), + DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), + + DECL(AL_DROP_UNMATCHED_SOFT), + DECL(AL_REMIX_UNMATCHED_SOFT), + + DECL(AL_AMBISONIC_LAYOUT_SOFT), + DECL(AL_AMBISONIC_SCALING_SOFT), + DECL(AL_FUMA_SOFT), + DECL(AL_ACN_SOFT), + DECL(AL_SN3D_SOFT), + DECL(AL_N3D_SOFT), + + DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), + DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), + + DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), + + DECL(AL_EFFECT_CONVOLUTION_REVERB_SOFT), + DECL(AL_EFFECTSLOT_STATE_SOFT), + + DECL(AL_FORMAT_UHJ2CHN8_SOFT), + DECL(AL_FORMAT_UHJ2CHN16_SOFT), + DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ3CHN8_SOFT), + DECL(AL_FORMAT_UHJ3CHN16_SOFT), + DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ4CHN8_SOFT), + DECL(AL_FORMAT_UHJ4CHN16_SOFT), + DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), + DECL(AL_STEREO_MODE_SOFT), + DECL(AL_NORMAL_SOFT), + DECL(AL_SUPER_STEREO_SOFT), + DECL(AL_SUPER_STEREO_WIDTH_SOFT), + + DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), + DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), + DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_ALAW_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 @@ -804,82 +947,24 @@ 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_block_alignment " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFTX_effect_chain " - "AL_SOFTX_events " - "AL_SOFTX_filter_gain_ex " - "AL_SOFT_gain_clamp_ex " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize"; +std::string alcDefaultAllDevicesSpecifier; +std::string alcCaptureDefaultDeviceSpecifier; std::atomic<ALCenum> LastNullDeviceError{ALC_NO_ERROR}; -/* Thread-local current context */ -class ThreadCtx { - ALCcontext *ctx{nullptr}; - -public: - ~ThreadCtx() - { - if(ctx) - { - const bool result{ctx->releaseIfNoDelete()}; - ERR("Context %p current for thread being destroyed%s!\n", - decltype(std::declval<void*>()){ctx}, result ? "" : ", leak detected"); - } - } - - ALCcontext *get() const noexcept { return ctx; } - void set(ALCcontext *ctx_) noexcept { ctx = ctx_; } -}; -thread_local ThreadCtx LocalContext; -/* Process-wide current context */ -std::atomic<ALCcontext*> GlobalContext{nullptr}; - /* Flag to trap ALC device errors */ bool TrapALCError{false}; /* One-time configuration init control */ std::once_flag alc_config_once{}; -/* Default effect that applies to sources that don't have an effect on send 0 */ -ALeffect DefaultEffect; - /* Flag to specify if alcSuspendContext/alcProcessContext should defer/process * updates. */ bool SuspendDefers{true}; /* Initial seed for dithering. */ -constexpr ALuint DitherRNGSeed{22222u}; +constexpr uint DitherRNGSeed{22222u}; /************************************************ @@ -889,8 +974,11 @@ constexpr ALCchar alcNoDeviceExtList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " + "ALC_EXT_EFX " "ALC_EXT_thread_local_context " - "ALC_SOFT_loopback"; + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_reopen_device"; constexpr ALCchar alcExtensionList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -902,23 +990,16 @@ constexpr ALCchar alcExtensionList[] = "ALC_SOFT_device_clock " "ALC_SOFT_HRTF " "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " "ALC_SOFT_output_limiter " - "ALC_SOFT_pause_device"; -constexpr ALCint alcMajorVersion = 1; -constexpr ALCint alcMinorVersion = 1; + "ALC_SOFT_output_mode " + "ALC_SOFT_pause_device " + "ALC_SOFT_reopen_device"; +constexpr int alcMajorVersion{1}; +constexpr int alcMinorVersion{1}; -constexpr ALCint alcEFXMajorVersion = 1; -constexpr ALCint alcEFXMinorVersion = 0; - - -/* To avoid extraneous allocations, a 0-sized FlexArray<ALCcontext*> is defined - * globally as a sharable object. MSVC warns that a zero-sized array will have - * zero objects here, so silence that. - */ -DIAGNOSTIC_PUSH -msc_pragma(warning(disable : 4815)) -al::FlexArray<ALCcontext*> EmptyContextArray{0u}; -DIAGNOSTIC_POP +constexpr int alcEFXMajorVersion{1}; +constexpr int alcEFXMinorVersion{0}; using DeviceRef = al::intrusive_ptr<ALCdevice>; @@ -927,8 +1008,8 @@ using DeviceRef = al::intrusive_ptr<ALCdevice>; /************************************************ * Device lists ************************************************/ -al::vector<DeviceRef> DeviceList; -al::vector<ContextRef> ContextList; +al::vector<ALCdevice*> DeviceList; +al::vector<ALCcontext*> ContextList; std::recursive_mutex ListLock; @@ -938,33 +1019,45 @@ void alc_initconfig(void) if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { long lvl = strtol(loglevel->c_str(), nullptr, 0); - if(lvl >= NoLog && lvl <= LogRef) + if(lvl >= static_cast<long>(LogLevel::Trace)) + gLogLevel = LogLevel::Trace; + else if(lvl <= static_cast<long>(LogLevel::Disable)) + gLogLevel = LogLevel::Disable; + else gLogLevel = static_cast<LogLevel>(lvl); } - if(auto logfile = al::getenv("ALSOFT_LOGFILE")) - { #ifdef _WIN32 - std::wstring wname{utf8_to_wstr(logfile->c_str())}; - FILE *logf{_wfopen(wname.c_str(), L"wt")}; + if(const auto logfile = al::getenv(L"ALSOFT_LOGFILE")) + { + FILE *logf{_wfopen(logfile->c_str(), L"wt")}; + if(logf) gLogFile = logf; + else + { + auto u8name = wstr_to_utf8(logfile->c_str()); + ERR("Failed to open log file '%s'\n", u8name.c_str()); + } + } #else + if(const auto logfile = al::getenv("ALSOFT_LOGFILE")) + { FILE *logf{fopen(logfile->c_str(), "wt")}; -#endif if(logf) gLogFile = logf; else ERR("Failed to open log file '%s'\n", logfile->c_str()); } +#endif TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); { - al::string names; - if(std::begin(BackendList) == BackendListEnd) - names += "(none)"; + std::string names; + if(al::size(BackendList) < 1) + names = "(none)"; else { - const al::span<const BackendInfo> infos{std::begin(BackendList), BackendListEnd}; - names += infos[0].name; - for(const auto &backend : infos.subspan(1)) + const al::span<const BackendInfo> infos{BackendList}; + names = infos[0].name; + for(const auto &backend : infos.subspan<1>()) { names += ", "; names += backend.name; @@ -1033,18 +1126,79 @@ void alc_initconfig(void) } while(next++); } } - FillCPUCaps(capfilter); + if(auto cpuopt = GetCPUInfo()) + { + if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty()) + { + TRACE("Vendor ID: \"%s\"\n", cpuopt->mVendor.c_str()); + TRACE("Name: \"%s\"\n", cpuopt->mName.c_str()); + } + const int caps{cpuopt->mCaps}; + TRACE("Extensions:%s%s%s%s%s%s\n", + ((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), + ((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), + ((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), + ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), + ((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), + ((!capfilter) ? " -none-" : "")); + CPUCapFlags = caps & capfilter; + } -#ifdef _WIN32 -#define DEF_MIXER_PRIO 1 -#else -#define DEF_MIXER_PRIO 0 -#endif - RTPrioLevel = ConfigValueInt(nullptr, nullptr, "rt-prio").value_or(DEF_MIXER_PRIO); -#undef DEF_MIXER_PRIO + 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); + }; + sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"); + 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, ConfigValueFloat(nullptr, "game_compat", "nfc-scale").value_or(1.0f)); + } + Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); + + auto uhjfiltopt = ConfigValueStr(nullptr, "uhj", "decode-filter"); + if(!uhjfiltopt) + { + if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "filter"))) + WARN("uhj/filter is deprecated, please use uhj/decode-filter\n"); + } + if(uhjfiltopt) + { + if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + UhjDecodeQuality = UhjQualityType::FIR256; + else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + UhjDecodeQuality = UhjQualityType::FIR512; + else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + UhjDecodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); + } + if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "encode-filter"))) + { + if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + UhjEncodeQuality = UhjQualityType::FIR256; + else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + UhjEncodeQuality = UhjQualityType::FIR512; + else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + UhjEncodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); + } auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 @@ -1076,6 +1230,7 @@ void alc_initconfig(void) ReverbBoost *= std::pow(10.0f, valf / 20.0f); } + auto BackendListEnd = std::end(BackendList); auto devopt = al::getenv("ALSOFT_DRIVERS"); if(devopt || (devopt=ConfigValueStr(nullptr, nullptr, "drivers"))) { @@ -1130,16 +1285,16 @@ void alc_initconfig(void) BackendListEnd = backendlist_cur; } - auto init_backend = [](BackendInfo &backend) -> bool + auto init_backend = [](BackendInfo &backend) -> void { if(PlaybackFactory && CaptureFactory) - return true; + return; BackendFactory &factory = backend.getFactory(); if(!factory.init()) { WARN("Failed to initialize backend \"%s\"\n", backend.name); - return true; + return; } TRACE("Initialized backend \"%s\"\n", backend.name); @@ -1153,9 +1308,8 @@ void alc_initconfig(void) CaptureFactory = &factory; TRACE("Added \"%s\" for capture\n", backend.name); } - return false; }; - BackendListEnd = std::remove_if(std::begin(BackendList), BackendListEnd, init_backend); + std::for_each(std::begin(BackendList), BackendListEnd, init_backend); LoopbackBackendFactory::getFactory().init(); @@ -1179,17 +1333,44 @@ void alc_initconfig(void) { if(len == strlen(effectitem.name) && strncmp(effectitem.name, str, len) == 0) - DisabledEffects[effectitem.type] = AL_TRUE; + DisabledEffects[effectitem.type] = true; } } 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((DisabledEffects[EAXREVERB_EFFECT] || DisabledEffects[CHORUS_EFFECT]) + && eax_g_is_enabled) + { + eax_g_is_enabled = false; + TRACE("EAX disabled because %s disabled.\n", + (DisabledEffects[EAXREVERB_EFFECT] && DisabledEffects[CHORUS_EFFECT]) + ? "EAXReverb and Chorus are" : + DisabledEffects[EAXREVERB_EFFECT] ? "EAXReverb is" : + DisabledEffects[CHORUS_EFFECT] ? "Chorus is" : ""); + } + } +#endif // ALSOFT_EAX } -#define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) +inline void InitConfig() +{ std::call_once(alc_config_once, [](){alc_initconfig();}); } /************************************************ @@ -1197,118 +1378,36 @@ void alc_initconfig(void) ************************************************/ void ProbeAllDevicesList() { - DO_INITCONFIG(); + InitConfig(); std::lock_guard<std::recursive_mutex> _{ListLock}; - alcAllDevicesList.clear(); - if(PlaybackFactory) - PlaybackFactory->probe(DevProbe::Playback, &alcAllDevicesList); + if(!PlaybackFactory) + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + else + { + std::string names{PlaybackFactory->probe(BackendType::Playback)}; + if(names.empty()) names += '\0'; + names.swap(alcAllDevicesList); + } } void ProbeCaptureDeviceList() { - DO_INITCONFIG(); + InitConfig(); std::lock_guard<std::recursive_mutex> _{ListLock}; - alcCaptureDeviceList.clear(); - if(CaptureFactory) - CaptureFactory->probe(DevProbe::Capture, &alcCaptureDeviceList); -} - -} // namespace - -/* Mixing thread piority level */ -ALint RTPrioLevel; - -FILE *gLogFile{stderr}; -#ifdef _DEBUG -LogLevel gLogLevel{LogWarning}; -#else -LogLevel gLogLevel{LogError}; -#endif - -/************************************************ - * Library initialization - ************************************************/ -#if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC) -BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) -{ - switch(reason) - { - case DLL_PROCESS_ATTACH: - /* Pin the DLL so we won't get unloaded until the process terminates */ - GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - reinterpret_cast<WCHAR*>(module), &module); - break; - } - return TRUE; -} -#endif - -/************************************************ - * Device format information - ************************************************/ -const ALCchar *DevFmtTypeString(DevFmtType type) noexcept -{ - switch(type) - { - case DevFmtByte: return "Signed Byte"; - case DevFmtUByte: return "Unsigned Byte"; - case DevFmtShort: return "Signed Short"; - case DevFmtUShort: return "Unsigned Short"; - case DevFmtInt: return "Signed Int"; - case DevFmtUInt: return "Unsigned Int"; - case DevFmtFloat: return "Float"; - } - return "(unknown type)"; -} -const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept -{ - switch(chans) + if(!CaptureFactory) + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + else { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - case DevFmtX51: return "5.1 Surround"; - case DevFmtX51Rear: return "5.1 Surround (Rear)"; - case DevFmtX61: return "6.1 Surround"; - case DevFmtX71: return "7.1 Surround"; - case DevFmtAmbi3D: return "Ambisonic 3D"; + std::string names{CaptureFactory->probe(BackendType::Capture)}; + if(names.empty()) names += '\0'; + names.swap(alcCaptureDeviceList); } - return "(unknown channels)"; } -ALuint BytesFromDevFmt(DevFmtType type) noexcept -{ - switch(type) - { - case DevFmtByte: return sizeof(ALbyte); - case DevFmtUByte: return sizeof(ALubyte); - case DevFmtShort: return sizeof(ALshort); - case DevFmtUShort: return sizeof(ALushort); - case DevFmtInt: return sizeof(ALint); - case DevFmtUInt: return sizeof(ALuint); - case DevFmtFloat: return sizeof(ALfloat); - } - return 0; -} -ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept -{ - switch(chans) - { - case DevFmtMono: return 1; - case DevFmtStereo: return 2; - case DevFmtQuad: return 4; - case DevFmtX51: return 6; - case DevFmtX51Rear: return 6; - case DevFmtX61: return 7; - case DevFmtX71: return 8; - case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); - } - return 0; -} struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; -static al::optional<DevFmtPair> DecomposeDevFormat(ALenum format) +al::optional<DevFmtPair> DecomposeDevFormat(ALenum format) { static const struct { ALenum format; @@ -1343,236 +1442,191 @@ static al::optional<DevFmtPair> DecomposeDevFormat(ALenum format) for(const auto &item : list) { if(item.format == format) - return al::make_optional(DevFmtPair{item.channels, item.type}); + return al::make_optional<DevFmtPair>({item.channels, item.type}); } return al::nullopt; } -static ALCboolean IsValidALCType(ALCenum type) +al::optional<DevFmtType> DevFmtTypeFromEnum(ALCenum type) { switch(type) { - case ALC_BYTE_SOFT: - case ALC_UNSIGNED_BYTE_SOFT: - case ALC_SHORT_SOFT: - case ALC_UNSIGNED_SHORT_SOFT: - case ALC_INT_SOFT: - case ALC_UNSIGNED_INT_SOFT: - case ALC_FLOAT_SOFT: - return ALC_TRUE; + case ALC_BYTE_SOFT: return DevFmtByte; + case ALC_UNSIGNED_BYTE_SOFT: return DevFmtUByte; + case ALC_SHORT_SOFT: return DevFmtShort; + case ALC_UNSIGNED_SHORT_SOFT: return DevFmtUShort; + case ALC_INT_SOFT: return DevFmtInt; + case ALC_UNSIGNED_INT_SOFT: return DevFmtUInt; + case ALC_FLOAT_SOFT: return DevFmtFloat; } - return ALC_FALSE; + WARN("Unsupported format type: 0x%04x\n", type); + return al::nullopt; +} +ALCenum EnumFromDevFmt(DevFmtType type) +{ + switch(type) + { + case DevFmtByte: return ALC_BYTE_SOFT; + case DevFmtUByte: return ALC_UNSIGNED_BYTE_SOFT; + case DevFmtShort: return ALC_SHORT_SOFT; + case DevFmtUShort: return ALC_UNSIGNED_SHORT_SOFT; + case DevFmtInt: return ALC_INT_SOFT; + case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT; + case DevFmtFloat: return ALC_FLOAT_SOFT; + } + throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; } -static ALCboolean IsValidALCChannels(ALCenum channels) +al::optional<DevFmtChannels> DevFmtChannelsFromEnum(ALCenum channels) { switch(channels) { - case ALC_MONO_SOFT: - case ALC_STEREO_SOFT: - case ALC_QUAD_SOFT: - case ALC_5POINT1_SOFT: - case ALC_6POINT1_SOFT: - case ALC_7POINT1_SOFT: - case ALC_BFORMAT3D_SOFT: - return ALC_TRUE; + case ALC_MONO_SOFT: return DevFmtMono; + case ALC_STEREO_SOFT: return DevFmtStereo; + case ALC_QUAD_SOFT: return DevFmtQuad; + case ALC_5POINT1_SOFT: return DevFmtX51; + case ALC_6POINT1_SOFT: return DevFmtX61; + case ALC_7POINT1_SOFT: return DevFmtX71; + case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } - return ALC_FALSE; + WARN("Unsupported format channels: 0x%04x\n", channels); + return al::nullopt; } - -static ALCboolean IsValidAmbiLayout(ALCenum layout) +ALCenum EnumFromDevFmt(DevFmtChannels channels) { - switch(layout) + switch(channels) { - case ALC_ACN_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; + case DevFmtMono: return ALC_MONO_SOFT; + case DevFmtStereo: return ALC_STEREO_SOFT; + case DevFmtQuad: return ALC_QUAD_SOFT; + case DevFmtX51: return ALC_5POINT1_SOFT; + case DevFmtX61: return ALC_6POINT1_SOFT; + case DevFmtX71: return ALC_7POINT1_SOFT; + case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; + /* FIXME: Shouldn't happen. */ + case DevFmtX714: + case DevFmtX3D71: break; } - return ALC_FALSE; + throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; } -static ALCboolean IsValidAmbiScaling(ALCenum scaling) +al::optional<DevAmbiLayout> DevAmbiLayoutFromEnum(ALCenum layout) { - switch(scaling) + switch(layout) { - case ALC_N3D_SOFT: - case ALC_SN3D_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; + case ALC_FUMA_SOFT: return DevAmbiLayout::FuMa; + case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } - return ALC_FALSE; + WARN("Unsupported ambisonic layout: 0x%04x\n", layout); + return al::nullopt; } - -/************************************************ - * Miscellaneous ALC helpers - ************************************************/ - -/* SetDefaultWFXChannelOrder - * - * Sets the default channel order used by WaveFormatEx. - */ -void SetDefaultWFXChannelOrder(ALCdevice *device) +ALCenum EnumFromDevAmbi(DevAmbiLayout layout) { - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); - - switch(device->FmtChans) + switch(layout) { - case DevFmtMono: - device->RealOut.ChannelIndex[FrontCenter] = 0; - break; - case DevFmtStereo: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - break; - case DevFmtQuad: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - break; - case DevFmtX51: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[SideLeft] = 4; - device->RealOut.ChannelIndex[SideRight] = 5; - break; - case DevFmtX51Rear: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackLeft] = 4; - device->RealOut.ChannelIndex[BackRight] = 5; - break; - case DevFmtX61: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackCenter] = 4; - device->RealOut.ChannelIndex[SideLeft] = 5; - device->RealOut.ChannelIndex[SideRight] = 6; - break; - case DevFmtX71: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackLeft] = 4; - device->RealOut.ChannelIndex[BackRight] = 5; - device->RealOut.ChannelIndex[SideLeft] = 6; - device->RealOut.ChannelIndex[SideRight] = 7; - break; - case DevFmtAmbi3D: - device->RealOut.ChannelIndex[Aux0] = 0; - if(device->mAmbiOrder > 0) - { - device->RealOut.ChannelIndex[Aux1] = 1; - device->RealOut.ChannelIndex[Aux2] = 2; - device->RealOut.ChannelIndex[Aux3] = 3; - } - if(device->mAmbiOrder > 1) - { - device->RealOut.ChannelIndex[Aux4] = 4; - device->RealOut.ChannelIndex[Aux5] = 5; - device->RealOut.ChannelIndex[Aux6] = 6; - device->RealOut.ChannelIndex[Aux7] = 7; - device->RealOut.ChannelIndex[Aux8] = 8; - } - if(device->mAmbiOrder > 2) - { - device->RealOut.ChannelIndex[Aux9] = 9; - device->RealOut.ChannelIndex[Aux10] = 10; - device->RealOut.ChannelIndex[Aux11] = 11; - device->RealOut.ChannelIndex[Aux12] = 12; - device->RealOut.ChannelIndex[Aux13] = 13; - device->RealOut.ChannelIndex[Aux14] = 14; - device->RealOut.ChannelIndex[Aux15] = 15; - } - break; + case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT; + case DevAmbiLayout::ACN: return ALC_ACN_SOFT; } + throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))}; } -/* SetDefaultChannelOrder - * - * Sets the default channel order used by most non-WaveFormatEx-based APIs. - */ -void SetDefaultChannelOrder(ALCdevice *device) +al::optional<DevAmbiScaling> DevAmbiScalingFromEnum(ALCenum scaling) { - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); - - switch(device->FmtChans) + switch(scaling) { - case DevFmtX51Rear: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - device->RealOut.ChannelIndex[FrontCenter] = 4; - device->RealOut.ChannelIndex[LFE] = 5; - return; - case DevFmtX71: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - device->RealOut.ChannelIndex[FrontCenter] = 4; - device->RealOut.ChannelIndex[LFE] = 5; - device->RealOut.ChannelIndex[SideLeft] = 6; - device->RealOut.ChannelIndex[SideRight] = 7; - return; - - /* Same as WFX order */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtQuad: - case DevFmtX51: - case DevFmtX61: - case DevFmtAmbi3D: - SetDefaultWFXChannelOrder(device); - break; + case ALC_FUMA_SOFT: return DevAmbiScaling::FuMa; + case ALC_SN3D_SOFT: return DevAmbiScaling::SN3D; + case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } + WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); + return al::nullopt; } - - -void ALCcontext::processUpdates() +ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) { - std::lock_guard<std::mutex> _{mPropLock}; - if(mDeferUpdates.exchange(false)) + switch(scaling) { - /* 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) - std::this_thread::yield(); - - 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); + case DevAmbiScaling::FuMa: return ALC_FUMA_SOFT; + case DevAmbiScaling::SN3D: return ALC_SN3D_SOFT; + case DevAmbiScaling::N3D: return ALC_N3D_SOFT; } + throw std::runtime_error{"Invalid DevAmbiScaling: "+std::to_string(int(scaling))}; } -/* alcSetError - * - * Stores the latest ALC device error +/* Downmixing channel arrays, to map the given format's missing channels to + * existing ones. Based on Wine's DSound downmix values, which are based on + * PulseAudio's. */ -static void alcSetError(ALCdevice *device, ALCenum errorCode) +constexpr std::array<InputRemixMap::TargetMix,2> FrontStereoSplit{{ + {FrontLeft, 0.5f}, {FrontRight, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,1> FrontLeft9{{ + {FrontLeft, 1.0f/9.0f} +}}; +constexpr std::array<InputRemixMap::TargetMix,1> FrontRight9{{ + {FrontRight, 1.0f/9.0f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> BackMonoToFrontSplit{{ + {FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> LeftStereoSplit{{ + {FrontLeft, 0.5f}, {BackLeft, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> RightStereoSplit{{ + {FrontRight, 0.5f}, {BackRight, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> BackStereoSplit{{ + {BackLeft, 0.5f}, {BackRight, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> SideStereoSplit{{ + {SideLeft, 0.5f}, {SideRight, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,1> ToSideLeft{{ + {SideLeft, 1.0f} +}}; +constexpr std::array<InputRemixMap::TargetMix,1> ToSideRight{{ + {SideRight, 1.0f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> BackLeftSplit{{ + {SideLeft, 0.5f}, {BackCenter, 0.5f} +}}; +constexpr std::array<InputRemixMap::TargetMix,2> BackRightSplit{{ + {SideRight, 0.5f}, {BackCenter, 0.5f} +}}; + +const std::array<InputRemixMap,6> StereoDownmix{{ + { FrontCenter, FrontStereoSplit }, + { SideLeft, FrontLeft9 }, + { SideRight, FrontRight9 }, + { BackLeft, FrontLeft9 }, + { BackRight, FrontRight9 }, + { BackCenter, BackMonoToFrontSplit }, +}}; +const std::array<InputRemixMap,4> QuadDownmix{{ + { FrontCenter, FrontStereoSplit }, + { SideLeft, LeftStereoSplit }, + { SideRight, RightStereoSplit }, + { BackCenter, BackStereoSplit }, +}}; +const std::array<InputRemixMap,3> X51Downmix{{ + { BackLeft, ToSideLeft }, + { BackRight, ToSideRight }, + { BackCenter, SideStereoSplit }, +}}; +const std::array<InputRemixMap,2> X61Downmix{{ + { BackLeft, BackLeftSplit }, + { BackRight, BackRightSplit }, +}}; +const std::array<InputRemixMap,1> X71Downmix{{ + { BackCenter, BackStereoSplit }, +}}; + + +/** Stores the latest ALC device error. */ +void alcSetError(ALCdevice *device, ALCenum errorCode) { - WARN("Error generated on device %p, code 0x%04x\n", decltype(std::declval<void*>()){device}, - errorCode); + WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); if(TrapALCError) { #ifdef _WIN32 @@ -1591,21 +1645,34 @@ static void alcSetError(ALCdevice *device, ALCenum errorCode) } -static std::unique_ptr<Compressor> CreateDeviceLimiter(const ALCdevice *device, const ALfloat threshold) +std::unique_ptr<Compressor> CreateDeviceLimiter(const ALCdevice *device, const float threshold) { - return CompressorInit(static_cast<ALuint>(device->RealOut.Buffer.size()), - static_cast<float>(device->Frequency), AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, 0.001f, - 0.002f, 0.0f, 0.0f, threshold, INFINITY, 0.0f, 0.020f, 0.200f); + static constexpr bool AutoKnee{true}; + static constexpr bool AutoAttack{true}; + static constexpr bool AutoRelease{true}; + static constexpr bool AutoPostGain{true}; + static constexpr bool AutoDeclip{true}; + static constexpr float LookAheadTime{0.001f}; + static constexpr float HoldTime{0.002f}; + static constexpr float PreGainDb{0.0f}; + static constexpr float PostGainDb{0.0f}; + static constexpr float Ratio{std::numeric_limits<float>::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<float>(device->Frequency), + AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, LookAheadTime, HoldTime, + PreGainDb, PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); } -/* UpdateClockBase - * +/** * Updates the device's base clock time with however many samples have been * done. This is used so frequency changes on the device don't cause the time * to jump forward or back. Must not be called while the device is running/ * mixing. */ -static inline void UpdateClockBase(ALCdevice *device) +inline void UpdateClockBase(ALCdevice *device) { IncrementRef(device->MixCount); device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency; @@ -1613,117 +1680,248 @@ static inline void UpdateClockBase(ALCdevice *device) IncrementRef(device->MixCount); } -/* UpdateDeviceParams - * +/** * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) +ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { - HrtfRequestMode hrtf_userreq{Hrtf_Default}; - HrtfRequestMode hrtf_appreq{Hrtf_Default}; - ALCenum gainLimiter{device->LimiterState}; - const ALCuint old_sends{device->NumAuxSends}; - ALCuint new_sends{device->NumAuxSends}; - DevFmtChannels oldChans; - DevFmtType oldType; - ALboolean update_failed; - ALCsizei hrtf_id{-1}; - ALCuint oldFreq; - - if((!attrList || !attrList[0]) && device->Type == Loopback) + if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device\n"); return ALC_INVALID_VALUE; } - // Check for attributes + uint numMono{device->NumMonoSources}; + uint numStereo{device->NumStereoSources}; + uint numSends{device->NumAuxSends}; + al::optional<StereoEncoding> stereomode; + al::optional<bool> optlimit; + al::optional<uint> optsrate; + al::optional<DevFmtChannels> optchans; + al::optional<DevFmtType> opttype; + al::optional<DevAmbiLayout> optlayout; + al::optional<DevAmbiScaling> optscale; + uint period_size{DEFAULT_UPDATE_SIZE}; + uint buffer_size{DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES}; + int hrtf_id{-1}; + uint aorder{0u}; + + if(device->Type != DeviceType::Loopback) + { + /* Get default settings from the user configuration */ + + if(auto freqopt = device->configValue<uint>(nullptr, "frequency")) + { + optsrate = clampu(*freqopt, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + + const double scale{static_cast<double>(*optsrate) / DEFAULT_OUTPUT_RATE}; + period_size = static_cast<uint>(period_size*scale + 0.5); + } + + if(auto persizeopt = device->configValue<uint>(nullptr, "period_size")) + period_size = clampu(*persizeopt, 64, 8192); + if(auto numperopt = device->configValue<uint>(nullptr, "periods")) + buffer_size = clampu(*numperopt, 2, 16) * period_size; + else + buffer_size = period_size * DEFAULT_NUM_UPDATES; + + if(auto typeopt = device->configValue<std::string>(nullptr, "sample-type")) + { + static constexpr struct TypeMap { + const char name[8]; + DevFmtType type; + } typelist[] = { + { "int8", DevFmtByte }, + { "uint8", DevFmtUByte }, + { "int16", DevFmtShort }, + { "uint16", DevFmtUShort }, + { "int32", DevFmtInt }, + { "uint32", DevFmtUInt }, + { "float32", DevFmtFloat }, + }; + + const ALCchar *fmt{typeopt->c_str()}; + auto iter = std::find_if(std::begin(typelist), std::end(typelist), + [fmt](const TypeMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(typelist)) + ERR("Unsupported sample-type: %s\n", fmt); + else + opttype = iter->type; + } + if(auto chanopt = device->configValue<std::string>(nullptr, "channels")) + { + static constexpr struct ChannelMap { + const char name[16]; + DevFmtChannels chans; + uint8_t order; + } chanlist[] = { + { "mono", DevFmtMono, 0 }, + { "stereo", DevFmtStereo, 0 }, + { "quad", DevFmtQuad, 0 }, + { "surround51", DevFmtX51, 0 }, + { "surround61", DevFmtX61, 0 }, + { "surround71", DevFmtX71, 0 }, + { "surround714", DevFmtX714, 0 }, + { "surround3d71", DevFmtX3D71, 0 }, + { "surround51rear", DevFmtX51, 0 }, + { "ambi1", DevFmtAmbi3D, 1 }, + { "ambi2", DevFmtAmbi3D, 2 }, + { "ambi3", DevFmtAmbi3D, 3 }, + }; + + const ALCchar *fmt{chanopt->c_str()}; + auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), + [fmt](const ChannelMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(chanlist)) + ERR("Unsupported channels: %s\n", fmt); + else + { + optchans = iter->chans; + aorder = iter->order; + } + } + if(auto ambiopt = device->configValue<std::string>(nullptr, "ambi-format")) + { + const ALCchar *fmt{ambiopt->c_str()}; + if(al::strcasecmp(fmt, "fuma") == 0) + { + optlayout = DevAmbiLayout::FuMa; + optscale = DevAmbiScaling::FuMa; + } + else if(al::strcasecmp(fmt, "acn+fuma") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::FuMa; + } + else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::SN3D; + } + else if(al::strcasecmp(fmt, "acn+n3d") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::N3D; + } + else + ERR("Unsupported ambi-format: %s\n", fmt); + } + + if(auto hrtfopt = device->configValue<std::string>(nullptr, "hrtf")) + { + WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); + + const char *hrtf{hrtfopt->c_str()}; + if(al::strcasecmp(hrtf, "true") == 0) + stereomode = StereoEncoding::Hrtf; + else if(al::strcasecmp(hrtf, "false") == 0) + { + if(!stereomode || *stereomode == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } + else if(al::strcasecmp(hrtf, "auto") != 0) + ERR("Unexpected hrtf value: %s\n", hrtf); + } + } + + if(auto encopt = device->configValue<std::string>(nullptr, "stereo-encoding")) + { + const char *mode{encopt->c_str()}; + if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "panpot") == 0) + stereomode = StereoEncoding::Basic; + else if(al::strcasecmp(mode, "uhj") == 0) + stereomode = StereoEncoding::Uhj; + else if(al::strcasecmp(mode, "hrtf") == 0) + stereomode = StereoEncoding::Hrtf; + else + ERR("Unexpected stereo-encoding: %s\n", mode); + } + + // Check for app-specified attributes if(attrList && attrList[0]) { - ALCenum alayout{AL_NONE}; - ALCenum ascale{AL_NONE}; - ALCenum schans{AL_NONE}; - ALCenum stype{AL_NONE}; - ALCsizei attrIdx{0}; - ALCuint aorder{0}; - ALCuint freq{0u}; - - ALuint numMono{device->NumMonoSources}; - ALuint numStereo{device->NumStereoSources}; - ALuint numSends{old_sends}; - -#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v) + ALenum outmode{ALC_ANY_SOFT}; + al::optional<bool> opthrtf; + int freqAttr{}; + +#define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); + size_t attrIdx{0}; while(attrList[attrIdx]) { switch(attrList[attrIdx]) { - case ALC_FORMAT_CHANNELS_SOFT: - schans = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, schans); + case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) + if(device->Type == DeviceType::Loopback) + optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; - case ALC_FORMAT_TYPE_SOFT: - stype = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, stype); + case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) + if(device->Type == DeviceType::Loopback) + opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; - case ALC_FREQUENCY: - freq = static_cast<ALuint>(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_FREQUENCY, freq); + case ATTRIBUTE(ALC_FREQUENCY) + freqAttr = attrList[attrIdx + 1]; break; - case ALC_AMBISONIC_LAYOUT_SOFT: - alayout = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, alayout); + case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) + if(device->Type == DeviceType::Loopback) + optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_SCALING_SOFT: - ascale = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, ascale); + case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) + if(device->Type == DeviceType::Loopback) + optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_ORDER_SOFT: - aorder = static_cast<ALuint>(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder); + case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) + if(device->Type == DeviceType::Loopback) + aorder = static_cast<uint>(attrList[attrIdx + 1]); break; - case ALC_MONO_SOURCES: - numMono = static_cast<ALuint>(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_MONO_SOURCES, numMono); + case ATTRIBUTE(ALC_MONO_SOURCES) + numMono = static_cast<uint>(attrList[attrIdx + 1]); if(numMono > INT_MAX) numMono = 0; break; - case ALC_STEREO_SOURCES: - numStereo = static_cast<ALuint>(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_STEREO_SOURCES, numStereo); + case ATTRIBUTE(ALC_STEREO_SOURCES) + numStereo = static_cast<uint>(attrList[attrIdx + 1]); if(numStereo > INT_MAX) numStereo = 0; break; - case ALC_MAX_AUXILIARY_SENDS: - numSends = static_cast<ALuint>(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends); + case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) + numSends = static_cast<uint>(attrList[attrIdx + 1]); if(numSends > INT_MAX) numSends = 0; else numSends = minu(numSends, MAX_SENDS); break; - case ALC_HRTF_SOFT: - TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_HRTF_SOFT) if(attrList[attrIdx + 1] == ALC_FALSE) - hrtf_appreq = Hrtf_Disable; + opthrtf = false; else if(attrList[attrIdx + 1] == ALC_TRUE) - hrtf_appreq = Hrtf_Enable; - else - hrtf_appreq = Hrtf_Default; + opthrtf = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + opthrtf = al::nullopt; break; - case ALC_HRTF_ID_SOFT: + case ATTRIBUTE(ALC_HRTF_ID_SOFT) hrtf_id = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id); break; - case ALC_OUTPUT_LIMITER_SOFT: - gainLimiter = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter); + case ATTRIBUTE(ALC_OUTPUT_LIMITER_SOFT) + if(attrList[attrIdx + 1] == ALC_FALSE) + optlimit = false; + else if(attrList[attrIdx + 1] == ALC_TRUE) + optlimit = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + optlimit = al::nullopt; + break; + + case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) + outmode = attrList[attrIdx + 1]; break; default: @@ -1734,131 +1932,129 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) attrIdx += 2; } -#undef TRACE_ATTR +#undef ATTRIBUTE - const bool loopback{device->Type == Loopback}; - if(loopback) + if(device->Type == DeviceType::Loopback) { - if(!schans || !stype || !freq) - { - WARN("Missing format for loopback device\n"); + if(!optchans || !opttype) return ALC_INVALID_VALUE; - } - if(!IsValidALCChannels(schans) || !IsValidALCType(stype) || freq < MIN_OUTPUT_RATE) + if(freqAttr < MIN_OUTPUT_RATE || freqAttr > MAX_OUTPUT_RATE) return ALC_INVALID_VALUE; - if(schans == ALC_BFORMAT3D_SOFT) + if(*optchans == DevFmtAmbi3D) { - if(!alayout || !ascale || !aorder) - { - WARN("Missing ambisonic info for loopback device\n"); + if(!optlayout || !optscale) return ALC_INVALID_VALUE; - } - if(!IsValidAmbiLayout(alayout) || !IsValidAmbiScaling(ascale)) + if(aorder < 1 || aorder > MaxAmbiOrder) return ALC_INVALID_VALUE; - if(aorder < 1 || aorder > MAX_AMBI_ORDER) - return ALC_INVALID_VALUE; - if((alayout == ALC_FUMA_SOFT || ascale == ALC_FUMA_SOFT) && aorder > 3) + if((*optlayout == DevAmbiLayout::FuMa || *optscale == DevAmbiScaling::FuMa) + && aorder > 3) return ALC_INVALID_VALUE; } - } - - /* If a context is already running on the device, stop playback so the - * device attributes can be updated. - */ - if(device->Flags.get<DeviceRunning>()) - device->Backend->stop(); - device->Flags.unset<DeviceRunning>(); - - UpdateClockBase(device); - - const char *devname{nullptr}; - if(!loopback) - { - devname = device->DeviceName.c_str(); - - 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.unset<FrequencyRequest>(); - else + else if(*optchans == DevFmtStereo) { - freq = maxu(freq, MIN_OUTPUT_RATE); - - device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / - device->Frequency; - device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / - device->Frequency; + if(opthrtf) + { + if(*opthrtf) + stereomode = StereoEncoding::Hrtf; + else + { + if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } + } - device->Frequency = freq; - device->Flags.set<FrequencyRequest>(); + 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; } - if(auto persizeopt = ConfigValueUInt(devname, nullptr, "period_size")) - device->UpdateSize = clampu(*persizeopt, 64, 8192); - - if(auto peropt = ConfigValueUInt(devname, nullptr, "periods")) - device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); + optsrate = static_cast<uint>(freqAttr); } else { - device->Frequency = freq; - device->FmtChans = static_cast<DevFmtChannels>(schans); - device->FmtType = static_cast<DevFmtType>(stype); - if(schans == ALC_BFORMAT3D_SOFT) + if(opthrtf) { - device->mAmbiOrder = aorder; - device->mAmbiLayout = static_cast<AmbiLayout>(alayout); - device->mAmbiScale = static_cast<AmbiNorm>(ascale); + if(*opthrtf) + stereomode = StereoEncoding::Hrtf; + else + { + if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } } - } - 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; + if(outmode != ALC_ANY_SOFT) + { + using OutputMode = ALCdevice::OutputMode; + switch(OutputMode(outmode)) + { + case OutputMode::Any: break; + case OutputMode::Mono: optchans = DevFmtMono; break; + case OutputMode::Stereo: optchans = DevFmtStereo; break; + case OutputMode::StereoBasic: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Basic; + break; + case OutputMode::Uhj2: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Uhj; + break; + case OutputMode::Hrtf: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Hrtf; + break; + case OutputMode::Quad: optchans = DevFmtQuad; break; + case OutputMode::X51: optchans = DevFmtX51; break; + case OutputMode::X61: optchans = DevFmtX61; break; + case OutputMode::X71: optchans = DevFmtX71; break; + } + } + + if(freqAttr) + { + uint oldrate = optsrate.value_or(DEFAULT_OUTPUT_RATE); + freqAttr = clampi(freqAttr, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + + const double scale{static_cast<double>(freqAttr) / oldrate}; + period_size = static_cast<uint>(period_size*scale + 0.5); + buffer_size = static_cast<uint>(buffer_size*scale + 0.5); + optsrate = static_cast<uint>(freqAttr); + } } - else - numMono = maxu(numMono, 256); - numStereo = minu(numStereo, numMono); - numMono -= numStereo; - device->SourcesMax = numMono + numStereo; - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; + /* If a context is already running on the device, stop playback so the + * device attributes can be updated. + */ + if(device->Flags.test(DeviceRunning)) + device->Backend->stop(); + device->Flags.reset(DeviceRunning); - if(auto sendsopt = ConfigValueInt(devname, nullptr, "sends")) - new_sends = minu(numSends, static_cast<ALuint>(clampi(*sendsopt, 0, MAX_SENDS))); - else - new_sends = numSends; + UpdateClockBase(device); } - if(device->Flags.get<DeviceRunning>()) + 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; - device->Stablizer = nullptr; device->Limiter = nullptr; - device->ChannelDelay.clear(); + device->ChannelDelays = nullptr; std::fill(std::begin(device->HrtfAccumData), std::end(device->HrtfAccumData), float2{}); device->Dry.AmbiMap.fill(BFChannelConfig{}); device->Dry.Buffer = {}; std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u); - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + device->RealOut.RemixMap = {}; + device->RealOut.ChannelIndex.fill(InvalidChannelIndex); device->RealOut.Buffer = {}; device->MixBuffer.clear(); device->MixBuffer.shrink_to_fit(); @@ -1869,166 +2065,188 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *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 */ - device->HrtfStatus = ALC_HRTF_DISABLED_SOFT; - if(device->Type != Loopback) + + if(device->Type == DeviceType::Loopback) { - if(auto hrtfopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf")) + device->Frequency = *optsrate; + device->FmtChans = *optchans; + device->FmtType = *opttype; + if(device->FmtChans == DevFmtAmbi3D) { - const char *hrtf{hrtfopt->c_str()}; - if(al::strcasecmp(hrtf, "true") == 0) - hrtf_userreq = Hrtf_Enable; - else if(al::strcasecmp(hrtf, "false") == 0) - hrtf_userreq = Hrtf_Disable; - else if(al::strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); + device->mAmbiOrder = aorder; + device->mAmbiLayout = *optlayout; + device->mAmbiScale = *optscale; } - - if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable)) + device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); + } + else + { + device->FmtType = opttype.value_or(DevFmtTypeDefault); + device->FmtChans = optchans.value_or(DevFmtChannelsDefault); + device->mAmbiOrder = 0; + device->BufferSize = buffer_size; + device->UpdateSize = period_size; + device->Frequency = optsrate.value_or(DEFAULT_OUTPUT_RATE); + device->Flags.set(FrequencyRequest, optsrate.has_value()) + .set(ChannelsRequest, optchans.has_value()) + .set(SampleTypeRequest, opttype.has_value()); + + if(device->FmtChans == DevFmtAmbi3D) { - HrtfEntry *hrtf{nullptr}; - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - if(!device->HrtfList.empty()) - { - if(hrtf_id >= 0 && static_cast<ALuint>(hrtf_id) < device->HrtfList.size()) - hrtf = GetLoadedHrtf(device->HrtfList[static_cast<ALuint>(hrtf_id)].hrtf); - else - hrtf = GetLoadedHrtf(device->HrtfList.front().hrtf); - } - - if(hrtf) - { - device->FmtChans = DevFmtStereo; - device->Frequency = hrtf->sampleRate; - device->Flags.set<ChannelsRequest, FrequencyRequest>(); - if(HrtfEntry *oldhrtf{device->mHrtf}) - oldhrtf->DecRef(); - device->mHrtf = hrtf; - } - else + device->mAmbiOrder = clampu(aorder, 1, MaxAmbiOrder); + device->mAmbiLayout = optlayout.value_or(DevAmbiLayout::Default); + device->mAmbiScale = optscale.value_or(DevAmbiScaling::Default); + if(device->mAmbiOrder > 3 + && (device->mAmbiLayout == DevAmbiLayout::FuMa + || device->mAmbiScale == DevAmbiScaling::FuMa)) { - hrtf_userreq = Hrtf_Default; - hrtf_appreq = Hrtf_Disable; - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + device->mAmbiOrder = 3; } } } - oldFreq = device->Frequency; - oldChans = device->FmtChans; - oldType = device->FmtType; - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", - device->Flags.get<ChannelsRequest>()?"*":"", DevFmtChannelsString(device->FmtChans), - device->Flags.get<SampleTypeRequest>()?"*":"", DevFmtTypeString(device->FmtType), - device->Flags.get<FrequencyRequest>()?"*":"", device->Frequency, + device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), + device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), + device->Flags.test(FrequencyRequest)?"*":"", device->Frequency, device->UpdateSize, device->BufferSize); + const uint oldFreq{device->Frequency}; + const DevFmtChannels oldChans{device->FmtChans}; + const DevFmtType oldType{device->FmtType}; try { - if(device->Backend->reset() == false) - return ALC_INVALID_DEVICE; + auto backend = device->Backend.get(); + if(!backend->reset()) + throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { - ERR("Device reset failed: %s\n", e.what()); + ERR("Device error: %s\n", e.what()); + device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; } - if(device->FmtChans != oldChans && device->Flags.get<ChannelsRequest>()) + if(device->FmtChans != oldChans && device->Flags.test(ChannelsRequest)) { ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans), DevFmtChannelsString(device->FmtChans)); - device->Flags.unset<ChannelsRequest>(); + device->Flags.reset(ChannelsRequest); } - if(device->FmtType != oldType && device->Flags.get<SampleTypeRequest>()) + if(device->FmtType != oldType && device->Flags.test(SampleTypeRequest)) { ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType), DevFmtTypeString(device->FmtType)); - device->Flags.unset<SampleTypeRequest>(); + device->Flags.reset(SampleTypeRequest); } - if(device->Frequency != oldFreq && device->Flags.get<FrequencyRequest>()) + if(device->Frequency != oldFreq && device->Flags.test(FrequencyRequest)) { WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); - device->Flags.unset<FrequencyRequest>(); + device->Flags.reset(FrequencyRequest); } TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, device->BufferSize); - aluInitRenderer(device, hrtf_id, hrtf_appreq, hrtf_userreq); + if(device->Type != DeviceType::Loopback) + { + if(auto modeopt = device->configValue<std::string>(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); + } + } + + aluInitRenderer(device, hrtf_id, stereomode); + + /* Calculate the max number of sources, and split them between the mono and + * stereo count given the requested number of stereo sources. + */ + if(auto srcsopt = device->configValue<uint>(nullptr, "sources")) + { + if(*srcsopt <= 0) numMono = 256; + else numMono = maxu(*srcsopt, 16); + } + else + { + numMono = minu(numMono, INT_MAX-numStereo); + numMono = maxu(numMono+numStereo, 256); + } + numStereo = minu(numStereo, numMono); + numMono -= numStereo; + device->SourcesMax = numMono + numStereo; + device->NumMonoSources = numMono; + device->NumStereoSources = numStereo; + + if(auto sendsopt = device->configValue<int>(nullptr, "sends")) + numSends = minu(numSends, static_cast<uint>(clampi(*sendsopt, 0, MAX_SENDS))); + device->NumAuxSends = numSends; - 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); - /* Enable the stablizer only for formats that have front-left, front-right, - * and front-center outputs. - */ switch(device->FmtChans) { - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "front-stablizer", 0)) - { - auto stablizer = al::make_unique<FrontStablizer>(); - /* Initialize band-splitting filters for the front-left and front- - * right channels, with a crossover at 5khz (could be higher). - */ - const ALfloat scale{5000.0f / static_cast<ALfloat>(device->Frequency)}; - - stablizer->LFilter.init(scale); - stablizer->RFilter = stablizer->LFilter; - - device->Stablizer = std::move(stablizer); - /* NOTE: Don't know why this has to be "copied" into a local static - * constexpr variable to avoid a reference on - * FrontStablizer::DelayLength... - */ - constexpr size_t StablizerDelay{FrontStablizer::DelayLength}; - device->FixedLatency += nanoseconds{seconds{StablizerDelay}} / device->Frequency; - } - break; - case DevFmtMono: + case DevFmtMono: break; case DevFmtStereo: - case DevFmtQuad: - case DevFmtAmbi3D: + if(!device->mUhjEncoder) + device->RealOut.RemixMap = StereoDownmix; break; + case DevFmtQuad: device->RealOut.RemixMap = QuadDownmix; break; + case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break; + case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; + case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX714: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX3D71: device->RealOut.RemixMap = X51Downmix; break; + case DevFmtAmbi3D: break; } - TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled"); - if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1)) + nanoseconds::rep sample_delay{0}; + if(auto *encoder{device->mUhjEncoder.get()}) + sample_delay += encoder->getDelay(); + + if(device->getConfigValueBool(nullptr, "dither", true)) { - ALint depth{ - ConfigValueInt(device->DeviceName.c_str(), nullptr, "dither-depth").value_or(0)}; + int depth{device->configValue<int>(nullptr, "dither-depth").value_or(0)}; if(depth <= 0) { switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - depth = 8; - break; - case DevFmtShort: - case DevFmtUShort: - depth = 16; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUByte: + depth = 8; + break; + case DevFmtShort: + case DevFmtUShort: + depth = 16; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + break; } } if(depth > 0) { depth = clampi(depth, 2, 24); - device->DitherDepth = std::pow(2.0f, static_cast<ALfloat>(depth-1)); + device->DitherDepth = std::pow(2.0f, static_cast<float>(depth-1)); } } if(!(device->DitherDepth > 0.0f)) @@ -2037,522 +2255,309 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); - device->LimiterState = gainLimiter; - if(auto limopt = ConfigValueBool(device->DeviceName.c_str(), nullptr, "output-limiter")) - gainLimiter = *limopt ? ALC_TRUE : ALC_FALSE; + if(!optlimit) + optlimit = device->configValue<bool>(nullptr, "output-limiter"); - /* Valid values for gainLimiter are ALC_DONT_CARE_SOFT, ALC_TRUE, and - * ALC_FALSE. For ALC_DONT_CARE_SOFT, use the limiter for integer-based - * output (where samples must be clamped), and don't for floating-point - * (which can take unclamped samples). + /* If the gain limiter is unset, use the limiter for integer-based output + * (where samples must be clamped), and don't for floating-point (which can + * take unclamped samples). */ - if(gainLimiter == ALC_DONT_CARE_SOFT) + if(!optlimit) { switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - case DevFmtShort: - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - gainLimiter = ALC_TRUE; - break; - case DevFmtFloat: - gainLimiter = ALC_FALSE; - break; + case DevFmtByte: + case DevFmtUByte: + case DevFmtShort: + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + optlimit = true; + break; + case DevFmtFloat: + break; } } - if(gainLimiter == ALC_FALSE) + if(optlimit.value_or(false) == false) TRACE("Output limiter disabled\n"); else { - ALfloat thrshld = 1.0f; + float thrshld{1.0f}; switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - thrshld = 127.0f / 128.0f; - break; - case DevFmtShort: - case DevFmtUShort: - thrshld = 32767.0f / 32768.0f; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUByte: + thrshld = 127.0f / 128.0f; + break; + case DevFmtShort: + case DevFmtUShort: + thrshld = 32767.0f / 32768.0f; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + break; } if(device->DitherDepth > 0.0f) thrshld -= 1.0f / device->DitherDepth; const float thrshld_dB{std::log10(thrshld) * 20.0f}; auto limiter = CreateDeviceLimiter(device, thrshld_dB); - /* Convert the lookahead from samples to nanosamples to nanoseconds. */ - device->FixedLatency += nanoseconds{seconds{limiter->getLookAhead()}} / device->Frequency; + + sample_delay += limiter->getLookAhead(); device->Limiter = std::move(limiter); TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB); } + /* Convert the sample delay from samples to nanosamples to nanoseconds. */ + device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency; TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); - /* Need to delay returning failure until replacement Send arrays have been - * allocated with the appropriate size. - */ - update_failed = AL_FALSE; FPUCtl mixer_mode{}; - for(ALCcontext *context : *device->mContexts.load()) + for(ContextBase *ctxbase : *device->mContexts.load()) { - if(context->mDefaultSlot) + auto *context = static_cast<ALCcontext*>(ctxbase); + + std::unique_lock<std::mutex> proplock{context->mPropLock}; + std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock}; + + /* Clear out unused effect slot clusters. */ + auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &cluster) + { + for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) + { + if(cluster[i].InUse) + return false; + } + return true; + }; + auto slotcluster_iter = std::remove_if(context->mEffectSlotClusters.begin(), + context->mEffectSlotClusters.end(), slot_cluster_not_in_use); + context->mEffectSlotClusters.erase(slotcluster_iter, context->mEffectSlotClusters.end()); + + /* Free all wet buffers. Any in use will be reallocated with an updated + * configuration in aluInitEffectPanning. + */ + for(auto&& slots : context->mEffectSlotClusters) { - ALeffectslot *slot = context->mDefaultSlot.get(); - aluInitEffectPanning(slot, device); + for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) + { + slots[i].mWetBuffer.clear(); + slots[i].mWetBuffer.shrink_to_fit(); + slots[i].Wet.Buffer = {}; + } + } - EffectState *state{slot->Effect.State}; + if(ALeffectslot *slot{context->mDefaultSlot.get()}) + { + aluInitEffectPanning(slot->mSlot, context); + + EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); + state->deviceUpdate(device, slot->Buffer); + slot->updateProps(context); } - std::unique_lock<std::mutex> proplock{context->mPropLock}; - std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock}; + if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) + std::fill_n(curarray->end(), curarray->size(), nullptr); for(auto &sublist : context->mEffectSlotList) { - uint64_t usemask = ~sublist.FreeMask; + uint64_t usemask{~sublist.FreeMask}; while(usemask) { - ALsizei idx = CTZ64(usemask); - ALeffectslot *slot = sublist.EffectSlots + idx; - + const int idx{al::countr_zero(usemask)}; + ALeffectslot *slot{sublist.EffectSlots + idx}; usemask &= ~(1_u64 << idx); - aluInitEffectPanning(slot, device); + aluInitEffectPanning(slot->mSlot, context); - EffectState *state{slot->Effect.State}; + EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); + state->deviceUpdate(device, slot->Buffer); + slot->updateProps(context); } } slotlock.unlock(); + const uint num_sends{device->NumAuxSends}; std::unique_lock<std::mutex> srclock{context->mSourceLock}; for(auto &sublist : context->mSourceList) { - uint64_t usemask = ~sublist.FreeMask; + uint64_t usemask{~sublist.FreeMask}; while(usemask) { - ALsizei idx = CTZ64(usemask); - ALsource *source = sublist.Sources + idx; - + const int idx{al::countr_zero(usemask)}; + ALsource *source{sublist.Sources + idx}; usemask &= ~(1_u64 << idx); - if(old_sends != device->NumAuxSends) + auto clear_send = [](ALsource::SendData &send) -> void { - if(source->Send.size() > device->NumAuxSends) - { - auto clear_send = [](ALsource::SendData &send) -> void - { - if(send.Slot) - DecrementRef(send.Slot->ref); - send.Slot = nullptr; - }; - auto send_begin = source->Send.begin() + - static_cast<ptrdiff_t>(device->NumAuxSends); - std::for_each(send_begin, source->Send.end(), clear_send); - } - - source->Send.resize(device->NumAuxSends, - {nullptr, 1.0f, 1.0f, LOWPASSFREQREF, 1.0f, HIGHPASSFREQREF}); - source->Send.shrink_to_fit(); - } + if(send.Slot) + DecrementRef(send.Slot->ref); + send.Slot = nullptr; + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + }; + auto send_begin = source->Send.begin() + static_cast<ptrdiff_t>(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. - */ - ALvoiceProps *vprops{context->mFreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)}; - while(vprops) - { - ALvoiceProps *next = vprops->next.load(std::memory_order_relaxed); - delete vprops; - vprops = next; - } - - if(device->NumAuxSends < old_sends) + auto voicelist = context->getVoicesSpan(); + for(Voice *voice : voicelist) { - const ALuint num_sends{device->NumAuxSends}; /* Clear extraneous property set sends. */ - auto clear_sends = [num_sends](ALvoice &voice) -> void + std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), + VoiceProps::SendData{}); + + std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); + for(auto &chandata : voice->mChans) { - std::fill(std::begin(voice.mProps.Send)+num_sends, std::end(voice.mProps.Send), - ALvoiceProps::SendData{}); + std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), + SendParams{}); + } - std::fill(voice.mSend.begin()+num_sends, voice.mSend.end(), ALvoice::TargetData{}); - auto clear_chan_sends = [num_sends](ALvoice::ChannelData &chandata) -> void - { - std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), - SendParams{}); - }; - std::for_each(voice.mChans.begin(), voice.mChans.end(), clear_chan_sends); - }; - std::for_each(context->mVoices.begin(), context->mVoices.end(), clear_sends); - } - auto reset_voice = [device](ALvoice &voice) -> void - { - 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. */ - ALvoice::State vstate{ALvoice::Stopping}; - voice.mPlayState.compare_exchange_strong(vstate, ALvoice::Stopped, + Voice::State vstate{Voice::Stopping}; + voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped, std::memory_order_acquire, std::memory_order_acquire); - if(voice.mSourceID.load(std::memory_order_relaxed) == 0u) - return; + if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) + continue; - if(device->AvgSpeakerDist > 0.0f) - { - /* Reinitialize the NFC filters for new parameters. */ - const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC / - (device->AvgSpeakerDist * static_cast<float>(device->Frequency))}; - auto init_nfc = [w1](ALvoice::ChannelData &chandata) -> void - { chandata.mDryParams.NFCtrlFilter.init(w1); }; - std::for_each(voice.mChans.begin(), voice.mChans.begin()+voice.mNumChannels, - init_nfc); - } - }; - std::for_each(context->mVoices.begin(), context->mVoices.end(), reset_voice); + 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(); - if(update_failed) - return ALC_INVALID_DEVICE; - if(!device->Flags.get<DevicePaused>()) + if(!device->Flags.test(DevicePaused)) { try { auto backend = device->Backend.get(); - if(!backend->start()) - throw al::backend_exception{ALC_INVALID_DEVICE, "Backend error"}; - device->Flags.set<DeviceRunning>(); + backend->start(); + device->Flags.set(DeviceRunning); } catch(al::backend_exception& e) { - WARN("Failed to start playback: %s\n", e.what()); + 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} -{ -} - -/* ALCdevice::~ALCdevice - * - * Frees the device structure, and destroys any objects the app failed to - * delete. Called once there's no more references on the device. - */ -ALCdevice::~ALCdevice() -{ - TRACE("Freeing device %p\n", decltype(std::declval<void*>()){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<ALuint>(POPCNT64(~sublist.FreeMask)); } - )}; - if(count > 0) - WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, - [](size_t cur, const EffectSubList &sublist) noexcept -> size_t - { return cur + static_cast<ALuint>(POPCNT64(~sublist.FreeMask)); } - ); - if(count > 0) - WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, - [](size_t cur, const FilterSubList &sublist) noexcept -> size_t - { return cur + static_cast<ALuint>(POPCNT64(~sublist.FreeMask)); } - ); - if(count > 0) - WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); - - if(mHrtf) - mHrtf->DecRef(); - mHrtf = nullptr; - - auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); - if(oldarray != &EmptyContextArray) delete oldarray; -} - - -/* VerifyDevice - * - * Checks if the device handle is valid, and returns a new reference if so. +/** + * Updates device parameters as above, and also first clears the disconnected + * status, if set. */ -static DeviceRef VerifyDevice(ALCdevice *device) -{ - std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device); - if(iter != DeviceList.cend() && *iter == device) - return *iter; - return nullptr; -} - - -ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device) : mDevice{std::move(device)} +bool ResetDeviceParams(ALCdevice *device, const int *attrList) { - mPropsClean.test_and_set(std::memory_order_relaxed); -} - -ALCcontext::~ALCcontext() -{ - TRACE("Freeing context %p\n", decltype(std::declval<void*>()){this}); - - size_t count{0}; - ALcontextProps *cprops{mUpdate.exchange(nullptr, std::memory_order_relaxed)}; - if(cprops) - { - ++count; - delete cprops; - } - cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire); - while(cprops) + /* If the device was disconnected, reset it since we're opened anew. */ + if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY { - ALcontextProps *next{cprops->next.load(std::memory_order_relaxed)}; - delete cprops; - cprops = next; - ++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<ALuint>(POPCNT64(~sublist.FreeMask)); } - ); - if(count > 0) - WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); - mSourceList.clear(); - mNumSources = 0; - - count = 0; - ALeffectslotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; - while(eprops) - { - ALeffectslotProps *next{eprops->next.load(std::memory_order_relaxed)}; - if(eprops->State) eprops->State->release(); - delete eprops; - eprops = next; - ++count; - } - TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); - - delete mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed); - 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<ALuint>(POPCNT64(~sublist.FreeMask)); } - ); - if(count > 0) - WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); - mEffectSlotList.clear(); - mNumEffectSlots = 0; - - count = 0; - ALvoiceProps *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)}; - while(vprops) - { - ALvoiceProps *next{vprops->next.load(std::memory_order_relaxed)}; - delete vprops; - vprops = next; - ++count; - } - TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s"); - - mVoices.clear(); + /* Make sure disconnection is finished before continuing on. */ + device->waitForMix(); - count = 0; - ALlistenerProps *lprops{mListener.Params.Update.exchange(nullptr, std::memory_order_relaxed)}; - if(lprops) - { - ++count; - delete lprops; - } - lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire); - while(lprops) - { - ALlistenerProps *next{lprops->next.load(std::memory_order_relaxed)}; - delete lprops; - lprops = next; - ++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) + for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { - al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), evt_vec.first.len); - count += evt_vec.first.len; - } - if(evt_vec.second.len > 0) - { - al::destroy_n(reinterpret_cast<AsyncEvent*>(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); - } -} + auto *ctx = static_cast<ALCcontext*>(ctxbase); + if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + continue; -void ALCcontext::init() -{ - if(DefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == Playback) - { - mDefaultSlot = std::unique_ptr<ALeffectslot>{new ALeffectslot{}}; - if(InitEffectSlot(mDefaultSlot.get()) == AL_NO_ERROR) - aluInitEffectPanning(mDefaultSlot.get(), mDevice.get()); - else - { - mDefaultSlot = nullptr; - ERR("Failed to initialize the default effect slot\n"); + /* Clear any pending voice changes and reallocate voices to get a + * clean restart. + */ + std::lock_guard<std::mutex> __{ctx->mSourceLock}; + auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); + while(auto *next = vchg->mNext.load(std::memory_order_acquire)) + vchg = next; + ctx->mCurrentVoiceChange.store(vchg, std::memory_order_release); + + ctx->mVoicePropClusters.clear(); + ctx->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); + + ctx->mVoiceClusters.clear(); + ctx->allocVoices(std::max<size_t>(256, + ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); } - } - ALeffectslotArray *auxslots; - if(!mDefaultSlot) - auxslots = ALeffectslot::CreatePtrArray(0); - else - { - auxslots = ALeffectslot::CreatePtrArray(1); - (*auxslots)[0] = mDefaultSlot.get(); + device->Connected.store(true); } - mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); - - mExtensionList = alExtList; - - - mListener.Params.Matrix = alu::Matrix::Identity(); - mListener.Params.Velocity = alu::Vector{}; - mListener.Params.Gain = mListener.Gain; - mListener.Params.MetersPerUnit = mListener.mMetersPerUnit; - mListener.Params.DopplerFactor = mDopplerFactor; - mListener.Params.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; - mListener.Params.SourceDistanceModel = mSourceDistanceModel; - mListener.Params.mDistanceModel = mDistanceModel; - - - mAsyncEvents = CreateRingBuffer(511, sizeof(AsyncEvent), false); - StartEventThrd(this); + ALCenum err{UpdateDeviceParams(device, attrList)}; + if(err == ALC_NO_ERROR) LIKELY return ALC_TRUE; - mVoices.reserve(256); - mVoices.resize(64); + alcSetError(device, err); + return ALC_FALSE; } -bool ALCcontext::deinit() -{ - if(LocalContext.get() == this) - { - WARN("%p released while current on thread\n", decltype(std::declval<void*>()){this}); - LocalContext.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<size_t>(std::count(oldarray->begin(), oldarray->end(), this))) +/** Checks if the device handle is valid, and returns a new reference if so. */ +DeviceRef VerifyDevice(ALCdevice *device) +{ + std::lock_guard<std::recursive_mutex> _{ListLock}; + auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); + if(iter != DeviceList.end() && *iter == device) { - using ContextArray = al::FlexArray<ALCcontext*>; - 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<ALCcontext*>{}, _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) - { - while((mDevice->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - delete oldarray; - } - - ret = !newarray->empty(); + (*iter)->add_ref(); + return DeviceRef{*iter}; } - else - ret = !oldarray->empty(); - - StopEventThrd(this); - - return ret; + return nullptr; } -/* VerifyContext - * +/** * Checks if the given context is valid, returning a new reference to it if so. */ -static ContextRef VerifyContext(ALCcontext *context) +ContextRef VerifyContext(ALCcontext *context) { std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context); - if(iter != ContextList.cend() && *iter == context) - return *iter; + auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); + if(iter != ContextList.end() && *iter == context) + { + (*iter)->add_ref(); + return ContextRef{*iter}; + } return nullptr; } -/* GetContextRef - * - * Returns a new reference to the currently active context for this thread. - */ +} // namespace + +/** Returns a new reference to the currently active context for this thread. */ ContextRef GetContextRef(void) { - ALCcontext *context{LocalContext.get()}; + ALCcontext *context{ALCcontext::getThreadContext()}; if(context) context->add_ref(); else { - std::lock_guard<std::recursive_mutex> _{ListLock}; - context = GlobalContext.load(std::memory_order_acquire); - if(context) context->add_ref(); + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't trying to change the + * current context and bring its refcount to 0. + */ + } + context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); + if(context) LIKELY context->add_ref(); + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); } return ContextRef{context}; } @@ -2562,10 +2567,6 @@ ContextRef GetContextRef(void) * Standard ALC functions ************************************************/ -/* alcGetError - * - * Return last ALC generated error code for the given device - */ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) START_API_FUNC { @@ -2576,11 +2577,7 @@ START_API_FUNC END_API_FUNC -/* alcSuspendContext - * - * Suspends updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) START_API_FUNC { if(!SuspendDefers) @@ -2590,15 +2587,14 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else + { + std::lock_guard<std::mutex> _{ctx->mPropLock}; ctx->deferUpdates(); + } } END_API_FUNC -/* alcProcessContext - * - * Resumes processing updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) START_API_FUNC { if(!SuspendDefers) @@ -2608,19 +2604,18 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else + { + std::lock_guard<std::mutex> _{ctx->mPropLock}; ctx->processUpdates(); + } } END_API_FUNC -/* alcGetString - * - * Returns information about the device, and error strings - */ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) START_API_FUNC { - const ALCchar *value = nullptr; + const ALCchar *value{nullptr}; switch(param) { @@ -2654,7 +2649,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<std::mutex> _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeAllDevicesList(); @@ -2664,7 +2669,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<std::mutex> _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeCaptureDeviceList(); @@ -2706,7 +2719,7 @@ START_API_FUNC if(DeviceRef dev{VerifyDevice(Device)}) { std::lock_guard<std::mutex> _{dev->StateLock}; - value = (dev->mHrtf ? dev->HrtfName.c_str() : ""); + value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); } else alcSetError(nullptr, ALC_INVALID_DEVICE); @@ -2722,16 +2735,7 @@ START_API_FUNC END_API_FUNC -static inline ALCsizei NumAttrsForDevice(ALCdevice *device) -{ - if(device->Type == Capture) return 9; - if(device->Type != Loopback) return 29; - if(device->FmtChans == DevFmtAmbi3D) - return 35; - return 29; -} - -static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCint> values) +static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<int> values) { size_t i; @@ -2752,6 +2756,16 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin 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: @@ -2771,26 +2785,25 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin default: alcSetError(nullptr, ALC_INVALID_ENUM); - return 0; } return 0; } - if(device->Type == Capture) + std::lock_guard<std::mutex> _{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<size_t>(NumAttrsForDevice(device))) + if(values.size() < MaxCaptureAttributes) alcSetError(device, ALC_INVALID_VALUE); else { - std::lock_guard<std::mutex> _{device->StateLock}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2800,6 +2813,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin values[i++] = ALC_CONNECTED; values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; + assert(i == MaxCaptureAttributes); } return i; @@ -2811,17 +2825,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 1; case ALC_CAPTURE_SAMPLES: - { - std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = static_cast<int>(device->Backend->availableSamples()); - } + values[0] = static_cast<int>(device->Backend->availableSamples()); return 1; case ALC_CONNECTED: - { - std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } + values[0] = device->Connected.load(std::memory_order_acquire); return 1; default: @@ -2831,6 +2839,12 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin } /* 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: @@ -2843,7 +2857,6 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin alcSetError(device, ALC_INVALID_VALUE); else { - std::lock_guard<std::mutex> _{device->StateLock}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2855,7 +2868,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin values[i++] = ALC_FREQUENCY; values[i++] = static_cast<int>(device->Frequency); - if(device->Type != Loopback) + if(device->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; values[i++] = static_cast<int>(device->Frequency / device->UpdateSize); @@ -2868,20 +2881,20 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin if(device->FmtChans == DevFmtAmbi3D) { values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = static_cast<ALCint>(device->mAmbiLayout); + values[i++] = EnumFromDevAmbi(device->mAmbiLayout); values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast<ALCint>(device->mAmbiScale); + values[i++] = EnumFromDevAmbi(device->mAmbiScale); values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = static_cast<ALCint>(device->mAmbiOrder); + values[i++] = static_cast<int>(device->mAmbiOrder); } values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = device->FmtChans; + values[i++] = EnumFromDevFmt(device->FmtChans); values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = device->FmtType; + values[i++] = EnumFromDevFmt(device->FmtType); } values[i++] = ALC_MONO_SOURCES; @@ -2891,19 +2904,22 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin values[i++] = static_cast<int>(device->NumStereoSources); values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = static_cast<ALCint>(device->NumAuxSends); + values[i++] = static_cast<int>(device->NumAuxSends); values[i++] = ALC_HRTF_SOFT; 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; values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT; - values[i++] = MAX_AMBI_ORDER; + values[i++] = MaxAmbiOrder; + + values[i++] = ALC_OUTPUT_MODE_SOFT; + values[i++] = static_cast<ALCenum>(device->getOutputMode1()); values[i++] = 0; } @@ -2930,19 +2946,16 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 1; case ALC_REFRESH: - if(device->Type == Loopback) + if(device->Type == DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } - { - std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = static_cast<int>(device->Frequency / device->UpdateSize); - } + values[0] = static_cast<int>(device->Frequency / device->UpdateSize); return 1; case ALC_SYNC: - if(device->Type == Loopback) + if(device->Type == DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; @@ -2951,43 +2964,43 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 1; case ALC_FORMAT_CHANNELS_SOFT: - if(device->Type != Loopback) + if(device->Type != DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = device->FmtChans; + values[0] = EnumFromDevFmt(device->FmtChans); return 1; case ALC_FORMAT_TYPE_SOFT: - if(device->Type != Loopback) + if(device->Type != DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = device->FmtType; + values[0] = EnumFromDevFmt(device->FmtType); return 1; case ALC_AMBISONIC_LAYOUT_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = static_cast<ALCint>(device->mAmbiLayout); + values[0] = EnumFromDevAmbi(device->mAmbiLayout); return 1; case ALC_AMBISONIC_SCALING_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = static_cast<ALCint>(device->mAmbiScale); + values[0] = EnumFromDevAmbi(device->mAmbiScale); return 1; case ALC_AMBISONIC_ORDER_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; @@ -3004,14 +3017,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 1; case ALC_MAX_AUXILIARY_SENDS: - values[0] = static_cast<ALCint>(device->NumAuxSends); + values[0] = static_cast<int>(device->NumAuxSends); return 1; case ALC_CONNECTED: - { - std::lock_guard<std::mutex> _{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: @@ -3019,16 +3029,13 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin 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<std::mutex> _{device->StateLock}; - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - values[0] = static_cast<ALCint>(minz(device->HrtfList.size(), - std::numeric_limits<ALCint>::max())); - } + device->enumerateHrtfs(); + values[0] = static_cast<int>(minz(device->mHrtfList.size(), + std::numeric_limits<int>::max())); return 1; case ALC_OUTPUT_LIMITER_SOFT: @@ -3036,7 +3043,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 1; case ALC_MAX_AMBISONIC_ORDER_SOFT: - values[0] = MAX_AMBI_ORDER; + values[0] = MaxAmbiOrder; + return 1; + + case ALC_OUTPUT_MODE_SOFT: + values[0] = static_cast<ALCenum>(device->getOutputMode1()); return 1; default: @@ -3045,10 +3056,6 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCin return 0; } -/* alcGetIntegerv - * - * Returns information about the device and the version of OpenAL - */ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) START_API_FUNC { @@ -3056,7 +3063,7 @@ START_API_FUNC if(size <= 0 || values == nullptr) alcSetError(dev.get(), ALC_INVALID_VALUE); else - GetIntegerv(dev.get(), param, {values, values+size}); + GetIntegerv(dev.get(), param, {values, static_cast<uint>(size)}); } END_API_FUNC @@ -3065,32 +3072,41 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) + { alcSetError(dev.get(), ALC_INVALID_VALUE); - else if(!dev || dev->Type == Capture) + return; + } + if(!dev || dev->Type == DeviceType::Capture) { - auto ivals = al::vector<ALCint>(static_cast<ALuint>(size)); - size_t got{GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()})}; - std::copy_n(ivals.begin(), got, values); + auto ivals = al::vector<int>(static_cast<uint>(size)); + 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<std::mutex> _{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<std::mutex> _{dev->StateLock}; values[i++] = ALC_FREQUENCY; values[i++] = dev->Frequency; - if(dev->Type != Loopback) + if(dev->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; values[i++] = dev->Frequency / dev->UpdateSize; @@ -3100,23 +3116,23 @@ 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; - values[i++] = static_cast<ALCint64SOFT>(dev->mAmbiLayout); + values[i++] = EnumFromDevAmbi(dev->mAmbiLayout); values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast<ALCint64SOFT>(dev->mAmbiScale); + values[i++] = EnumFromDevAmbi(dev->mAmbiScale); values[i++] = ALC_AMBISONIC_ORDER_SOFT; values[i++] = dev->mAmbiOrder; } - - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = dev->FmtChans; - - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = dev->FmtType; } values[i++] = ALC_MONO_SOURCES; @@ -3132,30 +3148,31 @@ 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<ALCenum>(device->getOutputMode1()); + values[i++] = 0; } break; case ALC_DEVICE_CLOCK_SOFT: - { std::lock_guard<std::mutex> _{dev->StateLock}; + { + uint samplecount, refcount; nanoseconds basecount; - ALuint samplecount; - ALuint refcount; do { - while(((refcount=ReadRef(dev->MixCount))&1) != 0) - std::this_thread::yield(); + refcount = dev->waitForMix(); basecount = dev->ClockBase; samplecount = dev->SamplesDone; } while(refcount != ReadRef(dev->MixCount)); @@ -3165,10 +3182,7 @@ START_API_FUNC break; case ALC_DEVICE_LATENCY_SOFT: - { std::lock_guard<std::mutex> _{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: @@ -3176,27 +3190,22 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); else { - std::lock_guard<std::mutex> _{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(); } break; default: - auto ivals = al::vector<ALCint>(static_cast<ALuint>(size)); - size_t got{GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()})}; - std::copy_n(ivals.begin(), got, values); + auto ivals = al::vector<int>(static_cast<uint>(size)); + if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) + std::copy_n(ivals.begin(), got, values); break; } } END_API_FUNC -/* alcIsExtensionPresent - * - * Determines if there is support for a particular extension - */ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) START_API_FUNC { @@ -3225,10 +3234,6 @@ START_API_FUNC END_API_FUNC -/* alcGetProcAddress - * - * Retrieves the function address for a particular extension function - */ ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) START_API_FUNC { @@ -3236,24 +3241,28 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return nullptr; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &func : alcFunctions) + for(const auto &func : eaxFunctions) { if(strcmp(func.funcName, funcName) == 0) return func.address; } } +#endif + for(const auto &func : alcFunctions) + { + if(strcmp(func.funcName, funcName) == 0) + return func.address; + } return nullptr; } END_API_FUNC -/* alcGetEnumValue - * - * Get the value for a particular ALC enumeration name - */ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) START_API_FUNC { @@ -3261,24 +3270,29 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return 0; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &enm : alcEnumerations) + for(const auto &enm : eaxEnumerations) { if(strcmp(enm.enumName, enumName) == 0) return enm.value; } } +#endif + for(const auto &enm : alcEnumerations) + { + if(strcmp(enm.enumName, enumName) == 0) + return enm.value; + } + return 0; } END_API_FUNC -/* alcCreateContext - * - * Create and attach a context to the given device. - */ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) START_API_FUNC { @@ -3288,7 +3302,7 @@ START_API_FUNC */ std::unique_lock<std::recursive_mutex> listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture || !dev->Connected.load(std::memory_order_relaxed)) + if(!dev || dev->Type == DeviceType::Capture || !dev->Connected.load(std::memory_order_relaxed)) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); @@ -3303,32 +3317,29 @@ START_API_FUNC if(err != ALC_NO_ERROR) { alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device update failure"); return nullptr; } ContextRef context{new ALCcontext{dev}}; context->init(); - if(auto volopt = ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust")) + if(auto volopt = dev->configValue<float>(nullptr, "volume-adjust")) { - const ALfloat valf{*volopt}; + const float valf{*volopt}; if(!std::isfinite(valf)) ERR("volume-adjust must be finite: %f\n", valf); else { - const ALfloat db{clampf(valf, -24.0f, 24.0f)}; + const float db{clampf(valf, -24.0f, 24.0f)}; if(db != valf) WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); context->mGainBoost = std::pow(10.0f, db/20.0f); TRACE("volume-adjust gain: %f\n", context->mGainBoost); } } - UpdateListenerProps(context.get()); { - using ContextArray = al::FlexArray<ALCcontext*>; + using ContextArray = al::FlexArray<ContextBase*>; /* Allocate a new context array, which holds 1 more than the current/ * old array. @@ -3347,10 +3358,9 @@ START_API_FUNC * to finish before deleting the old array. */ dev->mContexts.store(newarray.release()); - if(oldarray != &EmptyContextArray) + if(oldarray != &DeviceBase::sEmptyContextArray) { - while((dev->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); + dev->waitForMix(); delete oldarray; } } @@ -3359,27 +3369,25 @@ START_API_FUNC { std::lock_guard<std::recursive_mutex> _{ListLock}; auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); - ContextList.emplace(iter, context); + ContextList.emplace(iter, context.get()); } - if(context->mDefaultSlot) + if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - if(InitializeEffect(context.get(), context->mDefaultSlot.get(), &DefaultEffect) == AL_NO_ERROR) - UpdateEffectSlotProps(context->mDefaultSlot.get(), context.get()); + ALenum sloterr{slot->initEffect(ALCcontext::sDefaultEffect.type, + ALCcontext::sDefaultEffect.Props, context.get())}; + if(sloterr == AL_NO_ERROR) + slot->updateProps(context.get()); else ERR("Failed to initialize the default effect\n"); } - TRACE("Created context %p\n", decltype(std::declval<void*>()){context.get()}); - return context.get(); + TRACE("Created context %p\n", voidp{context.get()}); + return context.release(); } END_API_FUNC -/* alcDestroyContext - * - * Remove a context from its device - */ -ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) START_API_FUNC { std::unique_lock<std::recursive_mutex> listlock{ListLock}; @@ -3390,51 +3398,40 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } - /* Hold an extra reference to this context so it remains valid until the - * ListLock is released. + + /* Hold a reference to this context so it remains valid until the ListLock + * is released. */ - ContextRef ctx{std::move(*iter)}; + ContextRef ctx{*iter}; ContextList.erase(iter); - ALCdevice *Device{ctx->mDevice.get()}; + ALCdevice *Device{ctx->mALDevice.get()}; std::lock_guard<std::mutex> _{Device->StateLock}; - if(!ctx->deinit() && Device->Flags.get<DeviceRunning>()) + if(!ctx->deinit() && Device->Flags.test(DeviceRunning)) { Device->Backend->stop(); - Device->Flags.unset<DeviceRunning>(); + Device->Flags.reset(DeviceRunning); } } END_API_FUNC -/* alcGetCurrentContext - * - * Returns the currently active context on the calling thread - */ ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) START_API_FUNC { - ALCcontext *Context{LocalContext.get()}; - if(!Context) Context = GlobalContext.load(); + ALCcontext *Context{ALCcontext::getThreadContext()}; + if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } END_API_FUNC -/* alcGetThreadContext - * - * Returns the currently active thread-local context - */ +/** Returns the currently active thread-local context. */ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) START_API_FUNC -{ return LocalContext.get(); } +{ return ALCcontext::getThreadContext(); } END_API_FUNC -/* alcMakeContextCurrent - * - * Makes the given context the active process-wide context, and removes the - * thread-local context for the calling thread. - */ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) START_API_FUNC { @@ -3451,26 +3448,28 @@ START_API_FUNC } /* Release this reference (if any) to store it in the GlobalContext * pointer. Take ownership of the reference (if any) that was previously - * stored there. + * stored there, and let the reference go. */ - ctx = ContextRef{GlobalContext.exchange(ctx.release())}; + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't getting or trying to change + * the current context as its refcount is decremented. + */ + } + ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); - /* Reset (decrement) the previous global reference by replacing it with the - * thread-local context. Take ownership of the thread-local context - * reference (if any), clearing the storage to null. + /* Take ownership of the thread-local context reference (if any), clearing + * the storage to null. */ - ctx = ContextRef{LocalContext.get()}; - if(ctx) LocalContext.set(nullptr); + ctx = ContextRef{ALCcontext::getThreadContext()}; + if(ctx) ALCcontext::setThreadContext(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; } END_API_FUNC -/* alcSetThreadContext - * - * Makes the given context the active context for the current thread - */ +/** Makes the given context the active context for the current thread. */ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) START_API_FUNC { @@ -3486,18 +3485,14 @@ START_API_FUNC } } /* context's reference count is already incremented */ - ContextRef old{LocalContext.get()}; - LocalContext.set(ctx.release()); + ContextRef old{ALCcontext::getThreadContext()}; + ALCcontext::setThreadContext(ctx.release()); return ALC_TRUE; } END_API_FUNC -/* alcGetContextsDevice - * - * Returns the device that a particular context is attached to - */ ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) START_API_FUNC { @@ -3507,19 +3502,15 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return nullptr; } - return ctx->mDevice.get(); + return ctx->mALDevice.get(); } END_API_FUNC -/* alcOpenDevice - * - * Opens the named device. - */ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) START_API_FUNC { - DO_INITCONFIG(); + InitConfig(); if(!PlaybackFactory) { @@ -3529,6 +3520,7 @@ START_API_FUNC if(deviceName) { + TRACE("Opening playback device \"%s\"\n", deviceName); if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 #ifdef _WIN32 /* Some old Windows apps hardcode these expecting OpenAL to use a @@ -3539,11 +3531,25 @@ 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; } + else + TRACE("Opening default playback device\n"); + + const uint DefaultSends{ +#ifdef ALSOFT_EAX + eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : +#endif // ALSOFT_EAX + DEFAULT_SENDS + }; - DeviceRef device{new ALCdevice{Playback}}; + DeviceRef device{new ALCdevice{DeviceType::Playback}}; /* Set output format */ device->FmtChans = DevFmtChannelsDefault; @@ -3553,170 +3559,35 @@ START_API_FUNC device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; device->SourcesMax = 256; + device->NumStereoSources = 1; + device->NumMonoSources = device->SourcesMax - device->NumStereoSources; device->AuxiliaryEffectSlotMax = 64; - device->NumAuxSends = DEFAULT_SENDS; + device->NumAuxSends = DefaultSends; try { auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); + std::lock_guard<std::recursive_mutex> _{ListLock}; backend->open(deviceName); device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open playback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } - deviceName = device->DeviceName.c_str(); - if(auto chanopt = ConfigValueStr(deviceName, nullptr, "channels")) - { - static const struct ChannelMap { - const char name[16]; - DevFmtChannels chans; - ALuint 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(ALuint freq{ConfigValueUInt(deviceName, nullptr, "frequency").value_or(0)}) - { - if(freq < MIN_OUTPUT_RATE) - { - ERR("%uhz request clamped to %uhz minimum\n", freq, MIN_OUTPUT_RATE); - freq = MIN_OUTPUT_RATE; - } - device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / device->Frequency; - device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / device->Frequency; - device->Frequency = freq; - device->Flags.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 srcsopt = ConfigValueUInt(deviceName, nullptr, "sources")) - { - if(*srcsopt > 0) device->SourcesMax = *srcsopt; - } - - if(auto slotsopt = ConfigValueUInt(deviceName, nullptr, "slots")) - { - if(*slotsopt > 0) - device->AuxiliaryEffectSlotMax = minu(*slotsopt, INT_MAX); - } - - if(auto sendsopt = ConfigValueInt(deviceName, nullptr, "sends")) - device->NumAuxSends = clampu(DEFAULT_SENDS, 0, - static_cast<ALuint>(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 = AmbiLayout::FuMa; - device->mAmbiScale = AmbiNorm::FuMa; - } - } - else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::SN3D; - } - else if(al::strcasecmp(fmt, "acn+n3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::N3D; - } - else - ERR("Unsupported ambi-format: %s\n", fmt); - } - { std::lock_guard<std::recursive_mutex> _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.emplace(iter, device); + DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p, \"%s\"\n", decltype(std::declval<void*>()){device.get()}, - device->DeviceName.c_str()); - return device.get(); + TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + return device.release(); } END_API_FUNC -/* alcCloseDevice - * - * Closes the given device. - */ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) START_API_FUNC { @@ -3727,26 +3598,26 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } - if((*iter)->Type == Capture) + if((*iter)->Type == DeviceType::Capture) { - alcSetError(iter->get(), ALC_INVALID_DEVICE); + alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } /* Erase the device, and any remaining contexts left on it, from their * respective lists. */ - DeviceRef dev{std::move(*iter)}; + DeviceRef dev{*iter}; DeviceList.erase(iter); std::unique_lock<std::mutex> statelock{dev->StateLock}; al::vector<ContextRef> 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) { - orphanctxs.emplace_back(std::move(*ctxiter)); + orphanctxs.emplace_back(ContextRef{*ctxiter}); ContextList.erase(ctxiter); } } @@ -3754,14 +3625,14 @@ START_API_FUNC for(ContextRef &context : orphanctxs) { - WARN("Releasing orphaned context %p\n", decltype(std::declval<void*>()){context.get()}); + WARN("Releasing orphaned context %p\n", voidp{context.get()}); context->deinit(); } orphanctxs.clear(); - if(dev->Flags.get<DeviceRunning>()) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); + dev->Flags.reset(DeviceRunning); return ALC_TRUE; } @@ -3774,7 +3645,7 @@ END_API_FUNC ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) START_API_FUNC { - DO_INITCONFIG(); + InitConfig(); if(!CaptureFactory) { @@ -3790,12 +3661,15 @@ START_API_FUNC if(deviceName) { + TRACE("Opening capture device \"%s\"\n", deviceName); if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 || al::strcasecmp(deviceName, "openal-soft") == 0) deviceName = nullptr; } + else + TRACE("Opening default capture device\n"); - DeviceRef device{new ALCdevice{Capture}}; + DeviceRef device{new ALCdevice{DeviceType::Capture}}; auto decompfmt = DecomposeDevFormat(format); if(!decompfmt) @@ -3807,10 +3681,12 @@ START_API_FUNC device->Frequency = frequency; device->FmtChans = decompfmt->chans; device->FmtType = decompfmt->type; - device->Flags.set<FrequencyRequest, ChannelsRequest, SampleTypeRequest>(); + device->Flags.set(FrequencyRequest); + device->Flags.set(ChannelsRequest); + device->Flags.set(SampleTypeRequest); - device->UpdateSize = static_cast<ALuint>(samples); - device->BufferSize = static_cast<ALuint>(samples); + device->UpdateSize = static_cast<uint>(samples); + device->BufferSize = static_cast<uint>(samples); try { TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", @@ -3818,24 +3694,25 @@ START_API_FUNC device->Frequency, device->UpdateSize, device->BufferSize); auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); + std::lock_guard<std::recursive_mutex> _{ListLock}; backend->open(deviceName); device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open capture device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard<std::recursive_mutex> _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.emplace(iter, device); + DeviceList.emplace(iter, device.get()); } - TRACE("Created capture device %p, \"%s\"\n", decltype(std::declval<void*>()){device.get()}, - device->DeviceName.c_str()); - return device.get(); + TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + return device.release(); } END_API_FUNC @@ -3849,20 +3726,20 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } - if((*iter)->Type != Capture) + if((*iter)->Type != DeviceType::Capture) { - alcSetError(iter->get(), ALC_INVALID_DEVICE); + alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } - DeviceRef dev{std::move(*iter)}; + DeviceRef dev{*iter}; DeviceList.erase(iter); listlock.unlock(); std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); + dev->Flags.reset(DeviceRunning); return ALC_TRUE; } @@ -3872,7 +3749,7 @@ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; @@ -3881,16 +3758,16 @@ START_API_FUNC std::lock_guard<std::mutex> _{dev->StateLock}; if(!dev->Connected.load(std::memory_order_acquire)) alcSetError(dev.get(), ALC_INVALID_DEVICE); - else if(!dev->Flags.get<DeviceRunning>()) + else if(!dev->Flags.test(DeviceRunning)) { try { auto backend = dev->Backend.get(); - if(!backend->start()) - throw al::backend_exception{ALC_INVALID_DEVICE, "Device start failure"}; - dev->Flags.set<DeviceRunning>(); + backend->start(); + dev->Flags.set(DeviceRunning); } catch(al::backend_exception& e) { - aluHandleDisconnect(dev.get(), "%s", e.what()); + ERR("%s\n", e.what()); + dev->handleDisconnect("%s", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); } } @@ -3901,14 +3778,14 @@ ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); + dev->Flags.reset(DeviceRunning); } } END_API_FUNC @@ -3917,7 +3794,7 @@ ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; @@ -3934,16 +3811,14 @@ START_API_FUNC std::lock_guard<std::mutex> _{dev->StateLock}; BackendBase *backend{dev->Backend.get()}; - const auto usamples = static_cast<ALCuint>(samples); + const auto usamples = static_cast<uint>(samples); if(usamples > backend->availableSamples()) { alcSetError(dev.get(), ALC_INVALID_VALUE); return; } - auto *bbuffer = static_cast<al::byte*>(buffer); - if(ALCenum err{backend->captureSamples(bbuffer, usamples)}) - alcSetError(dev.get(), err); + backend->captureSamples(static_cast<al::byte*>(buffer), usamples); } END_API_FUNC @@ -3952,14 +3827,11 @@ END_API_FUNC * ALC loopback functions ************************************************/ -/* alcLoopbackOpenDeviceSOFT - * - * Open a loopback device, for manual rendering. - */ +/** Open a loopback device, for manual rendering. */ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) START_API_FUNC { - DO_INITCONFIG(); + InitConfig(); /* Make sure the device name, if specified, is us. */ if(deviceName && strcmp(deviceName, alcDefaultName) != 0) @@ -3968,11 +3840,18 @@ START_API_FUNC return nullptr; } - DeviceRef device{new ALCdevice{Loopback}}; + const uint DefaultSends{ +#ifdef ALSOFT_EAX + eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : +#endif // ALSOFT_EAX + DEFAULT_SENDS + }; + + DeviceRef device{new ALCdevice{DeviceType::Loopback}}; device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; - device->NumAuxSends = DEFAULT_SENDS; + device->NumAuxSends = DefaultSends; //Set output format device->BufferSize = 0; @@ -3982,21 +3861,6 @@ START_API_FUNC device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - if(auto srcsopt = ConfigValueUInt(nullptr, nullptr, "sources")) - { - if(*srcsopt > 0) device->SourcesMax = *srcsopt; - } - - if(auto slotsopt = ConfigValueUInt(nullptr, nullptr, "slots")) - { - if(*slotsopt > 0) - device->AuxiliaryEffectSlotMax = minu(*slotsopt, INT_MAX); - } - - if(auto sendsopt = ConfigValueInt(nullptr, nullptr, "sends")) - device->NumAuxSends = clampu(DEFAULT_SENDS, 0, - static_cast<ALuint>(clampi(*sendsopt, 0, MAX_SENDS))); - device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; @@ -4008,36 +3872,37 @@ START_API_FUNC } catch(al::backend_exception &e) { WARN("Failed to open loopback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard<std::recursive_mutex> _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.emplace(iter, device); + DeviceList.emplace(iter, device.get()); } - TRACE("Created loopback device %p\n", decltype(std::declval<void*>()){device.get()}); - return device.get(); + TRACE("Created loopback device %p\n", voidp{device.get()}); + return device.release(); } END_API_FUNC -/* alcIsRenderFormatSupportedSOFT - * +/** * Determines if the loopback device supports the given format for rendering. */ ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Loopback) + if(!dev || dev->Type != DeviceType::Loopback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else if(freq <= 0) alcSetError(dev.get(), ALC_INVALID_VALUE); else { - if(IsValidALCType(type) && IsValidALCChannels(channels) && freq >= MIN_OUTPUT_RATE) + if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value() + && freq >= MIN_OUTPUT_RATE && freq <= MAX_OUTPUT_RATE) return ALC_TRUE; } @@ -4045,24 +3910,19 @@ START_API_FUNC } END_API_FUNC -/* alcRenderSamplesSOFT - * +/** * Renders some samples into a buffer, using the format last set by the * attributes given to alcCreateContext. */ FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) START_API_FUNC { - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Loopback) - alcSetError(dev.get(), ALC_INVALID_DEVICE); + if(!device || device->Type != DeviceType::Loopback) + alcSetError(device, ALC_INVALID_DEVICE); else if(samples < 0 || (samples > 0 && buffer == nullptr)) - alcSetError(dev.get(), ALC_INVALID_VALUE); + alcSetError(device, ALC_INVALID_VALUE); else - { - BackendLockGuard _{*dev->Backend}; - aluMixData(dev.get(), buffer, static_cast<ALuint>(samples)); - } + device->renderSamples(buffer, static_cast<uint>(samples), device->channelsFromFmt()); } END_API_FUNC @@ -4071,58 +3931,56 @@ END_API_FUNC * ALC DSP pause/resume functions ************************************************/ -/* alcDevicePauseSOFT - * - * Pause the DSP to stop audio processing. - */ +/** Pause the DSP to stop audio processing. */ ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Playback) + if(!dev || dev->Type != DeviceType::Playback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - dev->Flags.set<DevicePaused>(); + dev->Flags.reset(DeviceRunning); + dev->Flags.set(DevicePaused); } } END_API_FUNC -/* alcDeviceResumeSOFT - * - * Resume the DSP to restart audio processing. - */ +/** Resume the DSP to restart audio processing. */ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Playback) + if(!dev || dev->Type != DeviceType::Playback) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } std::lock_guard<std::mutex> _{dev->StateLock}; - if(!dev->Flags.get<DevicePaused>()) + if(!dev->Flags.test(DevicePaused)) return; - dev->Flags.unset<DevicePaused>(); + dev->Flags.reset(DevicePaused); if(dev->mContexts.load()->empty()) return; try { auto backend = dev->Backend.get(); - if(!backend->start()) - throw al::backend_exception{ALC_INVALID_DEVICE, "Device start failure"}; - dev->Flags.set<DeviceRunning>(); + backend->start(); + dev->Flags.set(DeviceRunning); } catch(al::backend_exception& e) { - aluHandleDisconnect(dev.get(), "%s", e.what()); + 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 @@ -4131,21 +3989,18 @@ END_API_FUNC * ALC HRTF functions ************************************************/ -/* alcGetStringiSOFT - * - * Gets a string parameter at the given index. - */ +/** Gets a string parameter at the given index. */ ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture) + if(!dev || dev->Type == DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else switch(paramName) { case ALC_HRTF_SPECIFIER_SOFT: - if(index >= 0 && static_cast<size_t>(index) < dev->HrtfList.size()) - return dev->HrtfList[static_cast<ALuint>(index)].name.c_str(); + if(index >= 0 && static_cast<uint>(index) < dev->mHrtfList.size()) + return dev->mHrtfList[static_cast<uint>(index)].c_str(); alcSetError(dev.get(), ALC_INVALID_VALUE); break; @@ -4158,16 +4013,13 @@ START_API_FUNC } END_API_FUNC -/* alcResetDeviceSOFT - * - * Resets the given device output, using the specified attribute list. - */ +/** Resets the given device output, using the specified attribute list. */ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) START_API_FUNC { std::unique_lock<std::recursive_mutex> listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture) + if(!dev || dev->Type == DeviceType::Capture) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); @@ -4179,17 +4031,95 @@ START_API_FUNC /* Force the backend to stop mixing first since we're resetting. Also reset * the connected state so lost devices can attempt recover. */ - if(dev->Flags.get<DeviceRunning>()) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - device->Connected.store(true); + dev->Flags.reset(DeviceRunning); - ALCenum err{UpdateDeviceParams(dev.get(), attribs)}; - if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; + return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; +} +END_API_FUNC - alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device start failure"); - return ALC_FALSE; + +/************************************************ + * ALC device reopen functions + ************************************************/ + +/** Reopens the given device output, using the specified name and attribute list. */ +FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, + const ALCchar *deviceName, const ALCint *attribs) +START_API_FUNC +{ + if(deviceName) + { + if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0) + deviceName = nullptr; + } + + std::unique_lock<std::recursive_mutex> 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<std::mutex> _{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/alc/alcmain.h b/alc/alcmain.h deleted file mode 100644 index 30c5b835..00000000 --- a/alc/alcmain.h +++ /dev/null @@ -1,390 +0,0 @@ -#ifndef ALC_MAIN_H -#define ALC_MAIN_H - -#include <algorithm> -#include <array> -#include <atomic> -#include <chrono> -#include <cstdint> -#include <cstddef> -#include <memory> -#include <mutex> -#include <string> -#include <utility> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - -#include "albyte.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alspan.h" -#include "ambidefs.h" -#include "atomic.h" -#include "devformat.h" -#include "filters/splitter.h" -#include "hrtf.h" -#include "inprogext.h" -#include "intrusive_ptr.h" -#include "vector.h" - -class BFormatDec; -struct ALbuffer; -struct ALeffect; -struct ALfilter; -struct BackendBase; -struct Compressor; -struct EffectState; -struct Uhj2Encoder; -struct bs2b; - - -#define MIN_OUTPUT_RATE 8000 -#define DEFAULT_OUTPUT_RATE 44100 -#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 - - -enum DeviceType { - Playback, - Capture, - Loopback -}; - - -enum RenderMode { - NormalRender, - StereoPair, - HrtfRender -}; - - -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - -/* Maximum delay in samples for speaker distance compensation. */ -#define MAX_DELAY_LENGTH 1024 - -class DistanceComp { -public: - struct DistData { - ALfloat Gain{1.0f}; - ALuint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */ - ALfloat *Buffer{nullptr}; - }; - -private: - std::array<DistData,MAX_OUTPUT_CHANNELS> mChannels; - al::vector<ALfloat,16> mSamples; - -public: - void setSampleCount(size_t new_size) { mSamples.resize(new_size); } - void clear() noexcept - { - for(auto &chan : mChannels) - { - chan.Gain = 1.0f; - chan.Length = 0; - chan.Buffer = nullptr; - } - using SampleVecT = decltype(mSamples); - SampleVecT{}.swap(mSamples); - } - - ALfloat *getSamples() noexcept { return mSamples.data(); } - - al::span<DistData,MAX_OUTPUT_CHANNELS> as_span() { return mChannels; } -}; - -struct BFChannelConfig { - ALfloat Scale; - ALuint Index; -}; - -/* Size for temporary storage of buffer data, in ALfloats. Larger values need - * more memory, while smaller values may need more iterations. The value needs - * to be a sensible size, however, as it constrains the max stepping value used - * for mixing, as well as the maximum number of samples per mixing iteration. - */ -#define BUFFERSIZE 1024 - -using FloatBufferLine = std::array<float,BUFFERSIZE>; - -/* 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)! - */ -#define MAX_RESAMPLER_PADDING 48 - - -struct FrontStablizer { - static constexpr size_t DelayLength{256u}; - - alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength]; - - BandSplitter LFilter, RFilter; - alignas(16) float LSplit[2][BUFFERSIZE]; - alignas(16) float RSplit[2][BUFFERSIZE]; - - alignas(16) float TempBuf[BUFFERSIZE + DelayLength]; - - DEF_NEWDEL(FrontStablizer) -}; - - -struct MixParams { - /* Coefficient channel mapping for mixing to the buffer. */ - std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{}; - - al::span<FloatBufferLine> Buffer; -}; - -struct RealMixParams { - std::array<ALuint,MaxChannels> ChannelIndex{}; - - al::span<FloatBufferLine> Buffer; -}; - -enum { - // Frequency was requested by the app or config file - FrequencyRequest, - // Channel configuration was requested by the config file - ChannelsRequest, - // Sample type was requested by the config file - SampleTypeRequest, - - // Specifies if the DSP is paused at user request - DevicePaused, - // Specifies if the device is currently running - DeviceRunning, - - DeviceFlagsCount -}; - -struct ALCdevice : public al::intrusive_ref<ALCdevice> { - std::atomic<bool> Connected{true}; - const DeviceType Type{}; - - ALuint Frequency{}; - ALuint UpdateSize{}; - ALuint BufferSize{}; - - DevFmtChannels FmtChans{}; - DevFmtType FmtType{}; - ALboolean IsHeadphones{AL_FALSE}; - ALuint mAmbiOrder{0}; - /* For DevFmtAmbi* output only, specifies the channel order and - * normalization. - */ - AmbiLayout mAmbiLayout{AmbiLayout::Default}; - AmbiNorm mAmbiScale{AmbiNorm::Default}; - - ALCenum LimiterState{ALC_DONT_CARE_SOFT}; - - std::string DeviceName; - - // Device flags - al::bitfield<DeviceFlagsCount> Flags{}; - - std::string HrtfName; - al::vector<EnumeratedHrtf> HrtfList; - ALCenum HrtfStatus{ALC_FALSE}; - - std::atomic<ALCenum> LastError{ALC_NO_ERROR}; - - // Maximum number of sources that can be created - ALuint SourcesMax{}; - // Maximum number of slots that can be created - ALuint AuxiliaryEffectSlotMax{}; - - ALCuint NumMonoSources{}; - ALCuint NumStereoSources{}; - ALCuint NumAuxSends{}; - - // Map of Buffers for this device - std::mutex BufferLock; - al::vector<BufferSubList> BufferList; - - // Map of Effects for this device - std::mutex EffectLock; - al::vector<EffectSubList> EffectList; - - // Map of Filters for this device - std::mutex FilterLock; - al::vector<FilterSubList> FilterList; - - /* Rendering mode. */ - RenderMode mRenderMode{NormalRender}; - - /* The average speaker distance as determined by the ambdec configuration, - * HRTF data set, or the NFC-HOA reference delay. Only used for NFC. - */ - ALfloat AvgSpeakerDist{0.0f}; - - ALuint SamplesDone{0u}; - std::chrono::nanoseconds ClockBase{0}; - std::chrono::nanoseconds FixedLatency{0}; - - /* Temp storage used for mixer processing. */ - alignas(16) ALfloat SourceData[BUFFERSIZE + MAX_RESAMPLER_PADDING]; - alignas(16) ALfloat ResampledData[BUFFERSIZE]; - alignas(16) ALfloat FilteredData[BUFFERSIZE]; - union { - alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH]; - alignas(16) ALfloat NfcSampleData[BUFFERSIZE]; - }; - - /* Persistent storage for HRTF mixing. */ - alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH]; - - /* Mixing buffer used by the Dry mix and Real output. */ - al::vector<FloatBufferLine, 16> MixBuffer; - - /* The "dry" path corresponds to the main output. */ - MixParams Dry; - ALuint NumChannelsPerOrder[MAX_AMBI_ORDER+1]{}; - - /* "Real" output, which will be written to the device buffer. May alias the - * dry buffer. - */ - RealMixParams RealOut; - - /* HRTF state and info */ - std::unique_ptr<DirectHrtfState> mHrtfState; - HrtfEntry *mHrtf{nullptr}; - - /* Ambisonic-to-UHJ encoder */ - std::unique_ptr<Uhj2Encoder> Uhj_Encoder; - - /* Ambisonic decoder for speakers */ - std::unique_ptr<BFormatDec> AmbiDecoder; - - /* Stereo-to-binaural filter */ - std::unique_ptr<bs2b> Bs2b; - - using PostProc = void(ALCdevice::*)(const size_t SamplesToDo); - PostProc PostProcess{nullptr}; - - std::unique_ptr<FrontStablizer> Stablizer; - - std::unique_ptr<Compressor> Limiter; - - /* Delay buffers used to compensate for speaker distances. */ - DistanceComp ChannelDelay; - - /* Dithering control. */ - ALfloat DitherDepth{0.0f}; - ALuint DitherSeed{0u}; - - /* Running count of the mixer invocations, in 31.1 fixed point. This - * actually increments *twice* when mixing, first at the start and then at - * the end, so the bottom bit indicates if the device is currently mixing - * and the upper bits indicates how many mixes have been done. - */ - RefCount MixCount{0u}; - - // Contexts created on this device - std::atomic<al::FlexArray<ALCcontext*>*> 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<BackendBase> Backend; - - - ALCdevice(DeviceType type); - ALCdevice(const ALCdevice&) = delete; - ALCdevice& operator=(const ALCdevice&) = delete; - ~ALCdevice(); - - ALuint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } - ALuint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } - ALuint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } - - void ProcessHrtf(const size_t SamplesToDo); - void ProcessAmbiDec(const size_t SamplesToDo); - void ProcessUhj(const size_t SamplesToDo); - void ProcessBs2b(const size_t SamplesToDo); - - inline void postProcess(const size_t SamplesToDo) - { if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); } - - DEF_NEWDEL(ALCdevice) -}; - -/* Must be less than 15 characters (16 including terminating null) for - * compatibility with pthread_setname_np limitations. */ -#define MIXER_THREAD_NAME "alsoft-mixer" - -#define RECORD_THREAD_NAME "alsoft-record" - - -extern ALint RTPrioLevel; -void SetRTPriority(void); - -void SetDefaultChannelOrder(ALCdevice *device); -void SetDefaultWFXChannelOrder(ALCdevice *device); - -const ALCchar *DevFmtTypeString(DevFmtType type) noexcept; -const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept; - -/** - * GetChannelIdxByName - * - * Returns the index for the given channel name (e.g. FrontCenter), or - * INVALID_CHANNEL_INDEX if it doesn't exist. - */ -inline ALuint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept -{ return real.ChannelIndex[chan]; } -#define INVALID_CHANNEL_INDEX ~0u - - -al::vector<std::string> SearchDataFiles(const char *match, const char *subdir); - -#endif diff --git a/alc/alconfig.cpp b/alc/alconfig.cpp index ede39156..b0544b89 100644 --- a/alc/alconfig.cpp +++ b/alc/alconfig.cpp @@ -18,14 +18,6 @@ * Or go to http://www.gnu.org/copyleft/lgpl.html */ -#ifdef _WIN32 -#ifdef __MINGW32__ -#define _WIN32_IE 0x501 -#else -#define _WIN32_IE 0x400 -#endif -#endif - #include "config.h" #include "alconfig.h" @@ -33,7 +25,7 @@ #include <cstdlib> #include <cctype> #include <cstring> -#ifdef _WIN32_IE +#ifdef _WIN32 #include <windows.h> #include <shlobj.h> #endif @@ -48,8 +40,8 @@ #include "alfstream.h" #include "alstring.h" -#include "compat.h" -#include "logging.h" +#include "core/helpers.h" +#include "core/logging.h" #include "strutils.h" #include "vector.h" @@ -148,7 +140,7 @@ void LoadConfigFromFile(std::istream &f) if(buffer[0] == '[') { - char *line{&buffer[0]}; + auto line = const_cast<char*>(buffer.data()); char *section = line+1; char *endsection; @@ -224,35 +216,28 @@ void LoadConfigFromFile(std::istream &f) continue; } - auto cmtpos = buffer.find('#'); - if(cmtpos != std::string::npos) - buffer.resize(cmtpos); - while(!buffer.empty() && std::isspace(buffer.back())) - buffer.pop_back(); - if(buffer.empty()) continue; - - const char *line{&buffer[0]}; - char key[256]{}; - char value[256]{}; - if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 || - std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 || - std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2) - { - /* sscanf doesn't handle '' or "" as empty values, so clip it - * manually. */ - if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0) - value[0] = 0; - } - else if(std::sscanf(line, "%255[^=] %255[=]", key, value) == 2) + auto cmtpos = std::min(buffer.find('#'), buffer.size()); + while(cmtpos > 0 && std::isspace(buffer[cmtpos-1])) + --cmtpos; + if(!cmtpos) continue; + buffer.erase(cmtpos); + + auto sep = buffer.find('='); + if(sep == std::string::npos) { - /* Special case for 'key =' */ - value[0] = 0; + ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + continue; } - else + auto keyend = sep++; + while(keyend > 0 && std::isspace(buffer[keyend-1])) + --keyend; + if(!keyend) { - ERR(" config parse error: malformed option line: \"%s\"\n\n", line); + ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } + while(sep < buffer.size() && std::isspace(buffer[sep])) + sep++; std::string fullKey; if(!curSection.empty()) @@ -260,30 +245,84 @@ void LoadConfigFromFile(std::istream &f) fullKey += curSection; fullKey += '/'; } - fullKey += key; - while(!fullKey.empty() && std::isspace(fullKey.back())) - fullKey.pop_back(); + fullKey += buffer.substr(0u, keyend); - TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value); + std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; + if(value.size() > 1) + { + if((value.front() == '"' && value.back() == '"') + || (value.front() == '\'' && value.back() == '\'')) + { + value.pop_back(); + value.erase(value.begin()); + } + } + + TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str()); /* Check if we already have this option set */ - auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), - [&fullKey](const ConfigEntry &entry) -> bool - { return entry.key == fullKey; } - ); + auto find_key = [&fullKey](const ConfigEntry &entry) -> bool + { return entry.key == fullKey; }; + auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key); if(ent != ConfOpts.end()) { - if(value[0]) - ent->value = expdup(value); + if(!value.empty()) + ent->value = expdup(value.c_str()); else ConfOpts.erase(ent); } - else if(value[0]) - ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)}); + else if(!value.empty()) + ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())}); } ConfOpts.shrink_to_fit(); } +const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName) +{ + if(!keyName) + return nullptr; + + std::string key; + if(blockName && al::strcasecmp(blockName, "general") != 0) + { + key = blockName; + if(devName) + { + key += '/'; + key += devName; + } + key += '/'; + key += keyName; + } + else + { + if(devName) + { + key = devName; + key += '/'; + } + key += keyName; + } + + auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), + [&key](const ConfigEntry &entry) -> bool + { return entry.key == key; }); + if(iter != ConfOpts.cend()) + { + TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + if(!iter->value.empty()) + return iter->value.c_str(); + return nullptr; + } + + if(!devName) + { + TRACE("Key %s not found\n", key.c_str()); + return nullptr; + } + return GetConfigValue(nullptr, blockName, keyName); +} + } // namespace @@ -444,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<std::string> 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<std::string>(val); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return val; + return al::nullopt; } al::optional<int> 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<int>(std::strtol(val, nullptr, 0))); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return static_cast<int>(std::strtol(val, nullptr, 0)); + return al::nullopt; } al::optional<unsigned int> 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<unsigned int>(std::strtoul(val, nullptr, 0))); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return static_cast<unsigned int>(std::strtoul(val, nullptr, 0)); + return al::nullopt; } al::optional<float> 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 std::strtof(val, nullptr); + return al::nullopt; } al::optional<bool> 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::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/alc/alconfig.h b/alc/alconfig.h index ffc7adad..df2830cc 100644 --- a/alc/alconfig.h +++ b/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<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName); al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName); diff --git a/alc/alcontext.h b/alc/alcontext.h deleted file mode 100644 index ba3942f5..00000000 --- a/alc/alcontext.h +++ /dev/null @@ -1,196 +0,0 @@ -#ifndef ALCONTEXT_H -#define ALCONTEXT_H - -#include <atomic> -#include <cstddef> -#include <cstdint> -#include <memory> -#include <mutex> -#include <thread> -#include <utility> - -#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 "logging.h" -#include "threads.h" -#include "vector.h" -#include "voice.h" - -struct ALeffectslot; -struct ALeffectslotProps; -struct ALsource; -struct RingBuffer; - - -enum class DistanceModel { - InverseClamped = AL_INVERSE_DISTANCE_CLAMPED, - LinearClamped = AL_LINEAR_DISTANCE_CLAMPED, - ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED, - Inverse = AL_INVERSE_DISTANCE, - Linear = AL_LINEAR_DISTANCE, - Exponent = AL_EXPONENT_DISTANCE, - Disable = AL_NONE, - - Default = InverseClamped -}; - - -struct ALcontextProps { - ALfloat DopplerFactor; - ALfloat DopplerVelocity; - ALfloat SpeedOfSound; - ALboolean SourceDistanceModel; - DistanceModel mDistanceModel; - - std::atomic<ALcontextProps*> next; - - DEF_NEWDEL(ALcontextProps) -}; - - -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<ALCcontext> { - al::vector<SourceSubList> mSourceList; - ALuint mNumSources{0}; - std::mutex mSourceLock; - - al::vector<EffectSlotSubList> mEffectSlotList; - ALuint mNumEffectSlots{0u}; - std::mutex mEffectSlotLock; - - std::atomic<ALenum> mLastError{AL_NO_ERROR}; - - DistanceModel mDistanceModel{DistanceModel::Default}; - ALboolean mSourceDistanceModel{AL_FALSE}; - - ALfloat mDopplerFactor{1.0f}; - ALfloat mDopplerVelocity{1.0f}; - ALfloat mSpeedOfSound{SPEEDOFSOUNDMETRESPERSEC}; - - std::atomic_flag mPropsClean; - std::atomic<bool> mDeferUpdates{false}; - - std::mutex mPropLock; - - /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit - * indicates if updates are currently happening). - */ - RefCount mUpdateCount{0u}; - std::atomic<bool> mHoldUpdates{false}; - - ALfloat mGainBoost{1.0f}; - - std::atomic<ALcontextProps*> mUpdate{nullptr}; - - /* Linked lists of unused property containers, free to use for future - * updates. - */ - std::atomic<ALcontextProps*> mFreeContextProps{nullptr}; - std::atomic<ALlistenerProps*> mFreeListenerProps{nullptr}; - std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr}; - std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr}; - - al::vector<ALvoice> mVoices; - - using ALeffectslotArray = al::FlexArray<ALeffectslot*>; - std::atomic<ALeffectslotArray*> mActiveAuxSlots{nullptr}; - - std::thread mEventThread; - al::semaphore mEventSem; - std::unique_ptr<RingBuffer> mAsyncEvents; - std::atomic<ALbitfieldSOFT> mEnabledEvts{0u}; - std::mutex mEventCbLock; - ALEVENTPROCSOFT mEventCb{}; - void *mEventParam{nullptr}; - - /* Default effect slot */ - std::unique_ptr<ALeffectslot> mDefaultSlot; - - const al::intrusive_ptr<ALCdevice> mDevice; - const ALCchar *mExtensionList{nullptr}; - - ALlistener mListener{}; - - - ALCcontext(al::intrusive_ptr<ALCdevice> 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.store(true); } - - /** Resumes update processing after being deferred. */ - void processUpdates(); - - void setError(ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4); - - DEF_NEWDEL(ALCcontext) -}; - -#define SETERR_RETURN(ctx, err, retval, ...) do { \ - (ctx)->setError((err), __VA_ARGS__); \ - return retval; \ -} while(0) - - -using ContextRef = al::intrusive_ptr<ALCcontext>; - -ContextRef GetContextRef(void); - -void UpdateContextProps(ALCcontext *context); - - -extern bool TrapALError; - -#endif /* ALCONTEXT_H */ diff --git a/alc/alu.cpp b/alc/alu.cpp index 9bf052e1..e9ad68b1 100644 --- a/alc/alu.cpp +++ b/alc/alu.cpp @@ -25,9 +25,9 @@ #include <algorithm> #include <array> #include <atomic> +#include <cassert> #include <chrono> #include <climits> -#include <cmath> #include <cstdarg> #include <cstdio> #include <cstdlib> @@ -36,62 +36,83 @@ #include <limits> #include <memory> #include <new> -#include <numeric> +#include <stdint.h> #include <utility> -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/efx.h" - -#include "al/auxeffectslot.h" -#include "al/buffer.h" -#include "al/effect.h" -#include "al/event.h" -#include "al/listener.h" -#include "alcmain.h" -#include "alcontext.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" -#include "ambidefs.h" #include "atomic.h" -#include "bformatdec.h" -#include "bs2b.h" -#include "cpu_caps.h" -#include "devformat.h" -#include "effects/base.h" -#include "filters/biquad.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "fpu_modes.h" -#include "hrtf.h" -#include "inprogext.h" -#include "mastering.h" -#include "math_defs.h" -#include "mixer/defs.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/cubic_tables.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effects/base.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/filters/nfc.h" +#include "core/fpu_ctrl.h" +#include "core/hrtf.h" +#include "core/mastering.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" +#include "core/uhjfilter.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" #include "threads.h" -#include "uhjfilter.h" #include "vecmat.h" -#include "voice.h" +#include "vector.h" -#include "bsinc_inc.h" +struct CTag; +#ifdef HAVE_SSE +struct SSETag; +#endif +#ifdef HAVE_SSE2 +struct SSE2Tag; +#endif +#ifdef HAVE_SSE4_1 +struct SSE4Tag; +#endif +#ifdef HAVE_NEON +struct NEONTag; +#endif +struct PointTag; +struct LerpTag; +struct CubicTag; +struct BSincTag; +struct FastBSincTag; -static_assert(!(MAX_RESAMPLER_PADDING&1) && MAX_RESAMPLER_PADDING >= bsinc24.m[0], - "MAX_RESAMPLER_PADDING is not a multiple of two, or is too small"); +static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two"); namespace { +using uint = unsigned int; +using namespace std::chrono; + using namespace std::placeholders; -ALfloat InitConeScale() +float InitConeScale() { - ALfloat ret{1.0f}; + float ret{1.0f}; if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES")) { if(al::strcasecmp(optval->c_str(), "true") == 0 @@ -100,65 +121,31 @@ ALfloat InitConeScale() } return ret; } - -ALfloat InitZScale() -{ - ALfloat 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 ALfloat ConeScale{InitConeScale()}; +const float ConeScale{InitConeScale()}; -/* Localized Z scalar for mono sources */ -const ALfloat 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}; -MixerFunc MixSamples{Mix_<CTag>}; -RowMixerFunc MixRowSamples{MixRow_<CTag>}; +/* Source distance scale for NFC filters. */ +float NfcScale{1.0f}; -namespace { struct ChanMap { Channel channel; - ALfloat angle; - ALfloat elevation; + float angle; + float elevation; }; -HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_<CTag>; +using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, float *TempBuf, + HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); -inline MixerFunc SelectMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Mix_<NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return Mix_<SSETag>; -#endif - return Mix_<CTag>; -} - -inline RowMixerFunc SelectRowMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixRow_<NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixRow_<SSETag>; -#endif - return MixRow_<CTag>; -} +HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>}; inline HrtfDirectMixerFunc SelectHrtfMixer(void) { @@ -175,15 +162,15 @@ inline HrtfDirectMixerFunc SelectHrtfMixer(void) } -inline void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table) +inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table) { - size_t si{BSINC_SCALE_COUNT - 1}; + size_t si{BSincScaleCount - 1}; float sf{0.0f}; - if(increment > FRACTIONONE) + if(increment > MixerFracOne) { - sf = FRACTIONONE / static_cast<float>(increment); - sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange); + sf = MixerFracOne/static_cast<float>(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 @@ -198,7 +185,7 @@ inline void BsincPrepare(const ALuint increment, BsincState *state, const BSincT state->filter = table->Tab + table->filterOffset[si]; } -inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment) +inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) { switch(resampler) { @@ -219,33 +206,41 @@ inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment) #endif return Resample_<LerpTag,CTag>; case Resampler::Cubic: +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_<CubicTag,NEONTag>; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_<CubicTag,SSETag>; +#endif return Resample_<CubicTag,CTag>; case Resampler::BSinc12: case Resampler::BSinc24: - if(increment <= FRACTIONONE) + if(increment > MixerFracOne) { - /* fall-through */ - case Resampler::FastBSinc12: - case Resampler::FastBSinc24: #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_<FastBSincTag,NEONTag>; + return Resample_<BSincTag,NEONTag>; #endif #ifdef HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) - return Resample_<FastBSincTag,SSETag>; + return Resample_<BSincTag,SSETag>; #endif - return Resample_<FastBSincTag,CTag>; + return Resample_<BSincTag,CTag>; } + /* fall-through */ + case Resampler::FastBSinc12: + case Resampler::FastBSinc24: #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_<BSincTag,NEONTag>; + return Resample_<FastBSincTag,NEONTag>; #endif #ifdef HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) - return Resample_<BSincTag,SSETag>; + return Resample_<FastBSincTag,SSETag>; #endif - return Resample_<BSincTag,CTag>; + return Resample_<FastBSincTag,CTag>; } return Resample_<PointTag,CTag>; @@ -253,69 +248,85 @@ inline ResamplerFunc SelectResampler(Resampler resampler, ALuint increment) } // namespace -void aluInit(void) +void aluInit(CompatFlagBitset flags, const float nfcscale) { - MixSamples = SelectMixer(); - MixRowSamples = SelectRowMixer(); MixDirectHrtf = SelectHrtfMixer(); + XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f; + YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; + ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; + + NfcScale = clampf(nfcscale, 0.0001f, 10000.0f); } -ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state) +ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state) { switch(resampler) { case Resampler::Point: case Resampler::Linear: + break; case Resampler::Cubic: + state->cubic.filter = gCubicSpline.Tab.data(); break; case Resampler::FastBSinc12: case Resampler::BSinc12: - BsincPrepare(increment, &state->bsinc, &bsinc12); + BsincPrepare(increment, &state->bsinc, &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: - BsincPrepare(increment, &state->bsinc, &bsinc24); + BsincPrepare(increment, &state->bsinc, &gBSinc24); break; } return SelectResampler(resampler, increment); } -void ALCdevice::ProcessHrtf(const size_t SamplesToDo) +void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { /* HRTF is stereo output only. */ - const ALuint lidx{RealOut.ChannelIndex[FrontLeft]}; - const ALuint ridx{RealOut.ChannelIndex[FrontRight]}; + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, - mHrtfState.get(), 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::ProcessUhj(const size_t SamplesToDo) +void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) +{ + /* Decode with front image stablization. */ + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const uint cidx{RealOut.ChannelIndex[FrontCenter]}; + + AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx, + SamplesToDo); +} + +void DeviceBase::ProcessUhj(const size_t SamplesToDo) { /* UHJ is stereo output only. */ - const ALuint lidx{RealOut.ChannelIndex[FrontLeft]}; - const ALuint ridx{RealOut.ChannelIndex[FrontRight]}; + 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[0].data(), Dry.Buffer[1].data(), Dry.Buffer[2].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); /* BS2B is stereo output only. */ - const ALuint lidx{RealOut.ChannelIndex[FrontLeft]}; - const ALuint ridx{RealOut.ChannelIndex[FrontRight]}; + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), @@ -329,126 +340,145 @@ namespace { * and starting with a seed value of 22222, is suitable for generating * whitenoise. */ -inline ALuint dither_rng(ALuint *seed) noexcept +inline uint dither_rng(uint *seed) noexcept { *seed = (*seed * 96314165) + 907633515; return *seed; } -inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2) +/* Ambisonic upsampler function. It's effectively a matrix multiply. It takes + * an 'upsampler' and 'rotator' as the input matrices, and creates a matrix + * that behaves as if the B-Format input was first decoded to a speaker array + * at its input order, encoded back into the higher order mix, then finally + * rotated. + */ +void UpsampleBFormatTransform( + const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> output, + const al::span<const std::array<float,MaxAmbiChannels>> upsampler, + const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> rotator, size_t coeffs_order) { - return alu::Vector{ - in1[1]*in2[2] - in1[2]*in2[1], - in1[2]*in2[0] - in1[0]*in2[2], - in1[0]*in2[1] - in1[1]*in2[0], - 0.0f - }; + const size_t num_chans{AmbiChannelsFromOrder(coeffs_order)}; + for(size_t i{0};i < upsampler.size();++i) + output[i].fill(0.0f); + for(size_t i{0};i < upsampler.size();++i) + { + for(size_t k{0};k < num_chans;++k) + { + float *RESTRICT out{output[i].data()}; + /* Write the full number of channels. The compiler will have an + * easier time optimizing if it has a fixed length. + */ + for(size_t j{0};j < MaxAmbiChannels;++j) + out[j] += upsampler[i][k] * rotator[k][j]; + } + } } -inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2) + +inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { - return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2]; + 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(); } - -alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept +inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept { - return alu::Vector{ - vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], - vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], - vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], - vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3] - }; + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); } - -bool CalcContextParams(ALCcontext *Context) +inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - ALcontextProps *props{Context->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props) return false; - - ALlistener &Listener = Context->mListener; - Listener.Params.DopplerFactor = props->DopplerFactor; - Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; - - Listener.Params.SourceDistanceModel = props->SourceDistanceModel; - Listener.Params.mDistanceModel = props->mDistanceModel; - - AtomicReplaceHead(Context->mFreeContextProps, props); - return true; + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); + return AmbiIndex::FromACN2D(); } -bool CalcListenerParams(ALCcontext *Context) -{ - ALlistener &Listener = Context->mListener; - ALlistenerProps *props{Listener.Params.Update.exchange(nullptr, std::memory_order_acq_rel)}; +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{aluCrossproduct(N, V)}; + alu::Vector U{N.cross_product(V)}; U.normalize(); - Listener.Params.Matrix = alu::Matrix{ - U[0], V[0], -N[0], 0.0f, - U[1], V[1], -N[1], 0.0f, - U[2], V[2], -N[2], 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; + const alu::Matrix rot{ + U[0], V[0], -N[0], 0.0, + U[1], V[1], -N[1], 0.0, + U[2], V[2], -N[2], 0.0, + 0.0, 0.0, 0.0, 1.0}; + const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0}; + + ctx->mParams.Matrix = rot; + ctx->mParams.Velocity = rot * vel; - const alu::Vector P{Listener.Params.Matrix * - alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}}; - Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f); + ctx->mParams.Gain = props->Gain * ctx->mGainBoost; + ctx->mParams.MetersPerUnit = props->MetersPerUnit; + ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; - const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; - Listener.Params.Velocity = Listener.Params.Matrix * vel; + ctx->mParams.DopplerFactor = props->DopplerFactor; + ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; - Listener.Params.Gain = props->Gain * Context->mGainBoost; - Listener.Params.MetersPerUnit = props->MetersPerUnit; + ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; + ctx->mParams.mDistanceModel = props->mDistanceModel; - AtomicReplaceHead(Context->mFreeListenerProps, props); + AtomicReplaceHead(ctx->mFreeContextProps, props); return true; } -bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context) +bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBase *context) { - ALeffectslotProps *props{slot->Params.Update.exchange(nullptr, std::memory_order_acq_rel)}; + EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; - slot->Params.Gain = props->Gain; - slot->Params.AuxSendAuto = props->AuxSendAuto; - slot->Params.Target = props->Target; - slot->Params.EffectType = props->Type; - slot->Params.mEffectProps = props->Props; - if(IsReverbEffect(props->Type)) + /* If the effect slot target changed, clear the first sorted entry to force + * a re-sort. + */ + if(slot->Target != props->Target) + *sorted_slots = nullptr; + slot->Gain = props->Gain; + slot->AuxSendAuto = props->AuxSendAuto; + slot->Target = props->Target; + slot->EffectType = props->Type; + slot->mEffectProps = props->Props; + if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb) { - slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->Params.DecayTime = props->Props.Reverb.DecayTime; - slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; + slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor; + slot->DecayTime = props->Props.Reverb.DecayTime; + slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio; + slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio; + slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit; + slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; } else { - slot->Params.RoomRolloff = 0.0f; - slot->Params.DecayTime = 0.0f; - slot->Params.DecayLFRatio = 0.0f; - slot->Params.DecayHFRatio = 0.0f; - slot->Params.DecayHFLimit = AL_FALSE; - slot->Params.AirAbsorptionGainHF = 1.0f; + slot->RoomRolloff = 0.0f; + slot->DecayTime = 0.0f; + slot->DecayLFRatio = 0.0f; + slot->DecayHFRatio = 0.0f; + slot->DecayHFLimit = false; + slot->AirAbsorptionGainHF = 1.0f; } - EffectState *state{props->State}; - props->State = nullptr; - EffectState *oldstate{slot->Params.mEffectState}; - slot->Params.mEffectState = state; + EffectState *state{props->State.release()}; + EffectState *oldstate{slot->mEffectState.release()}; + slot->mEffectState.reset(state); /* Only release the old state if it won't get deleted, since we can't be * deleting/freeing anything in the mixer. @@ -458,12 +488,12 @@ bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context) /* Otherwise, if it would be deleted send it off with a release event. */ RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if LIKELY(evt_vec.first.len > 0) + if(evt_vec.first.len > 0) LIKELY { - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}}; + AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), + AsyncEvent::ReleaseEffectState)}; evt->u.mEffectState = oldstate; ring->writeAdvance(1); - context->mEventSem.post(); } else { @@ -472,21 +502,21 @@ bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context) * cleaned up sometime later (not ideal, but better than blocking * or leaking). */ - props->State = oldstate; + props->State.reset(oldstate); } } AtomicReplaceHead(context->mFreeEffectslotProps, props); EffectTarget output; - if(ALeffectslot *target{slot->Params.Target}) + if(EffectSlot *target{slot->Target}) output = EffectTarget{&target->Wet, nullptr}; else { - ALCdevice *device{context->mDevice.get()}; + DeviceBase *device{context->mDevice}; output = EffectTarget{&device->Dry, &device->RealOut}; } - state->update(context, slot, &slot->Params.mEffectProps, output); + state->update(context, slot, &slot->mEffectProps, output); return true; } @@ -496,20 +526,188 @@ bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context) */ inline float ScaleAzimuthFront(float azimuth, float scale) { - const ALfloat abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f)) - return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth); + const float abs_azi{std::fabs(azimuth)}; + if(!(abs_azi >= al::numbers::pi_v<float>*0.5f)) + return std::copysign(minf(abs_azi*scale, al::numbers::pi_v<float>*0.5f), azimuth); return azimuth; } -void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypos, - const ALfloat zpos, const ALfloat Distance, const ALfloat Spread, const ALfloat DryGain, - const ALfloat DryGainHF, const ALfloat DryGainLF, const ALfloat (&WetGain)[MAX_SENDS], - const ALfloat (&WetGainLF)[MAX_SENDS], const ALfloat (&WetGainHF)[MAX_SENDS], - ALeffectslot *(&SendSlots)[MAX_SENDS], const ALvoicePropsBase *props, - const ALlistener &Listener, const ALCdevice *Device) +/* Wraps the given value in radians to stay between [-pi,+pi] */ +inline float WrapRadians(float r) { - static const ChanMap MonoMap[1]{ + static constexpr float Pi{al::numbers::pi_v<float>}; + static constexpr float Pi2{Pi*2.0f}; + if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi; + if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2); + return r; +} + +/* Begin ambisonic rotation helpers. + * + * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation + * matrix. Higher orders, however, are more complicated. The method implemented + * here is a recursive algorithm (the rotation for first-order is used to help + * generate the second-order rotation, which helps generate the third-order + * rotation, etc). + * + * Adapted from + * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>, + * provided under the BSD 3-Clause license. + * + * Copyright (c) 2015, Archontis Politis + * Copyright (c) 2019, Christopher Robinson + * + * The u, v, and w coefficients used for generating higher-order rotations are + * precomputed since they're constant. The second-order coefficients are + * followed by the third-order coefficients, etc. + */ +template<size_t L> +constexpr size_t CalcRotatorSize() +{ return (L*2 + 1)*(L*2 + 1) + CalcRotatorSize<L-1>(); } + +template<> constexpr size_t CalcRotatorSize<0>() = delete; +template<> constexpr size_t CalcRotatorSize<1>() = delete; +template<> constexpr size_t CalcRotatorSize<2>() { return 5*5; } + +struct RotatorCoeffs { + struct CoeffValues { + float u, v, w; + }; + std::array<CoeffValues,CalcRotatorSize<MaxAmbiOrder>()> mCoeffs{}; + + RotatorCoeffs() + { + auto coeffs = mCoeffs.begin(); + + for(int l=2;l <= MaxAmbiOrder;++l) + { + for(int n{-l};n <= l;++n) + { + for(int m{-l};m <= l;++m) + { + // compute u,v,w terms of Eq.8.1 (Table I) + const bool d{m == 0}; // the delta function d_m0 + const float denom{static_cast<float>((std::abs(n) == l) ? + (2*l) * (2*l - 1) : (l*l - n*n))}; + + const int abs_m{std::abs(m)}; + coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom); + coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) * + static_cast<float>(l+abs_m) / denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f; + coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) * + static_cast<float>(l-abs_m) / denom) * (1.0f-d) * -0.5f; + ++coeffs; + } + } + } + } +}; +const RotatorCoeffs RotatorCoeffArray{}; + +/** + * Given the matrix, pre-filled with the (zeroth- and) first-order rotation + * coefficients, this fills in the coefficients for the higher orders up to and + * including the given order. The matrix is in ACN layout. + */ +void AmbiRotator(AmbiRotateMatrix &matrix, const int order) +{ + /* Don't do anything for < 2nd order. */ + if(order < 2) return; + + auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, + const AmbiRotateMatrix &R) + { + const float ri1{ R[ 1+2][static_cast<size_t>(i+2)]}; + const float rim1{R[-1+2][static_cast<size_t>(i+2)]}; + const float ri0{ R[ 0+2][static_cast<size_t>(i+2)]}; + + const size_t y{last_band + static_cast<size_t>(a+l-1)}; + if(n == -l) + return ri1*R[last_band][y] + rim1*R[last_band + static_cast<size_t>(l-1)*2][y]; + if(n == l) + return ri1*R[last_band + static_cast<size_t>(l-1)*2][y] - rim1*R[last_band][y]; + return ri0*R[last_band + static_cast<size_t>(n+l-1)][y]; + }; + + auto U = [P](const int l, const int m, const int n, const size_t last_band, + const AmbiRotateMatrix &R) + { + return P(0, l, m, n, last_band, R); + }; + auto V = [P](const int l, const int m, const int n, const size_t last_band, + const AmbiRotateMatrix &R) + { + using namespace al::numbers; + if(m > 0) + { + const bool d{m == 1}; + const float p0{P( 1, l, m-1, n, last_band, R)}; + const float p1{P(-1, l, -m+1, n, last_band, R)}; + return d ? p0*sqrt2_v<float> : (p0 - p1); + } + const bool d{m == -1}; + const float p0{P( 1, l, m+1, n, last_band, R)}; + const float p1{P(-1, l, -m-1, n, last_band, R)}; + return d ? p1*sqrt2_v<float> : (p0 + p1); + }; + auto W = [P](const int l, const int m, const int n, const size_t last_band, + const AmbiRotateMatrix &R) + { + assert(m != 0); + if(m > 0) + { + const float p0{P( 1, l, m+1, n, last_band, R)}; + const float p1{P(-1, l, -m-1, n, last_band, R)}; + return p0 + p1; + } + const float p0{P( 1, l, m-1, n, last_band, R)}; + const float p1{P(-1, l, -m+1, n, last_band, R)}; + return p0 - p1; + }; + + // compute rotation matrix of each subsequent band recursively + auto coeffs = RotatorCoeffArray.mCoeffs.cbegin(); + size_t band_idx{4}, last_band{1}; + for(int l{2};l <= order;++l) + { + size_t y{band_idx}; + for(int n{-l};n <= l;++n,++y) + { + size_t x{band_idx}; + for(int m{-l};m <= l;++m,++x) + { + float r{0.0f}; + + // computes Eq.8.1 + const float u{coeffs->u}; + if(u != 0.0f) r += u * U(l, m, n, last_band, matrix); + const float v{coeffs->v}; + if(v != 0.0f) r += v * V(l, m, n, last_band, matrix); + const float w{coeffs->w}; + if(w != 0.0f) r += w * W(l, m, n, last_band, matrix); + + matrix[y][x] = r; + ++coeffs; + } + } + last_band = band_idx; + band_idx += static_cast<uint>(l)*size_t{2} + 1; + } +} +/* End ambisonic rotation helpers. */ + + +constexpr float Deg2Rad(float x) noexcept +{ return static_cast<float>(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<const GainTriplet,MAX_SENDS> WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], + const VoiceProps *props, const ContextParams &Context, DeviceBase *Device) +{ + static constexpr ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } }, RearMap[2]{ { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, @@ -550,158 +748,134 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) } }; - const auto Frequency = static_cast<ALfloat>(Device->Frequency); - const ALuint NumSends{Device->NumAuxSends}; + const auto Frequency = static_cast<float>(Device->Frequency); + const uint NumSends{Device->NumAuxSends}; + + const size_t num_channels{voice->mChans.size()}; + ASSUME(num_channels > 0); + + for(auto &chandata : voice->mChans) + { + chandata.mDryParams.Hrtf.Target = HrtfFilter{}; + chandata.mDryParams.Gains.Target.fill(0.0f); + std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends, + [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); + } - bool DirectChannels{props->DirectChannels != AL_FALSE}; + DirectMode DirectChannels{props->DirectChannels}; const ChanMap *chans{nullptr}; - ALuint num_channels{0}; - bool isbformat{false}; - ALfloat downmix_gain{1.0f}; switch(voice->mFmtChannels) { case FmtMono: chans = MonoMap; - num_channels = 1; /* Mono buffers are never played direct. */ - DirectChannels = false; + DirectChannels = DirectMode::Off; break; case FmtStereo: - /* Convert counter-clockwise to clockwise. */ - StereoMap[0].angle = -props->StereoPan[0]; - StereoMap[1].angle = -props->StereoPan[1]; - + if(DirectChannels == DirectMode::Off) + { + /* Convert counter-clockwise to clock-wise, and wrap between + * [-pi,+pi]. + */ + StereoMap[0].angle = WrapRadians(-props->StereoPan[0]); + StereoMap[1].angle = WrapRadians(-props->StereoPan[1]); + } chans = StereoMap; - num_channels = 2; - downmix_gain = 1.0f / 2.0f; break; - case FmtRear: - chans = RearMap; - num_channels = 2; - downmix_gain = 1.0f / 2.0f; - break; - - case FmtQuad: - chans = QuadMap; - num_channels = 4; - downmix_gain = 1.0f / 4.0f; - break; - - case FmtX51: - chans = X51Map; - num_channels = 6; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 5.0f; - break; - - case FmtX61: - chans = X61Map; - num_channels = 7; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 6.0f; - break; - - case FmtX71: - chans = X71Map; - num_channels = 8; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 7.0f; - break; + case FmtRear: chans = RearMap; break; + case FmtQuad: chans = QuadMap; break; + case FmtX51: chans = X51Map; break; + case FmtX61: chans = X61Map; break; + case FmtX71: chans = X71Map; break; case FmtBFormat2D: - num_channels = 3; - isbformat = true; - DirectChannels = false; - break; - case FmtBFormat3D: - num_channels = 4; - isbformat = true; - DirectChannels = false; + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: + DirectChannels = DirectMode::Off; break; } - ASSUME(num_channels > 0); - std::for_each(voice->mChans.begin(), voice->mChans.begin()+num_channels, - [NumSends](ALvoice::ChannelData &chandata) -> void - { - chandata.mDryParams.Hrtf.Target = HrtfFilter{}; - chandata.mDryParams.Gains.Target.fill(0.0f); - std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends, - [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); - }); - - voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC); - if(isbformat) + voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); + if(auto *decoder{voice->mDecoder.get()}) + decoder->mWidthControl = minf(props->EnhWidth, 0.7f); + + if(IsAmbisonic(voice->mFmtChannels)) { - /* Special handling for B-Format sources. */ + /* Special handling for B-Format and UHJ sources. */ - if(Distance > std::numeric_limits<float>::epsilon()) + if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2 + && voice->mFmtChannels != FmtSuperStereo) { - /* Panning a B-Format sound toward some direction is easy. Just pan - * the first (W) channel as a normal mono sound and silence the - * others. - */ - - if(Device->AvgSpeakerDist > 0.0f) + if(!(Distance > std::numeric_limits<float>::epsilon())) + { + /* NOTE: The NFCtrlFilters were created with a w0 of 0, which + * is what we want for FOA input. The first channel may have + * been previously re-adjusted if panned, so reset it. + */ + voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f); + } + else { /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)}; + const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Only need to adjust the first channel of a B-Format source. */ voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0); - - voice->mFlags |= VOICE_HAS_NFC; } - ALfloat coeffs[MAX_AMBI_CHANNELS]; - if(Device->mRenderMode != StereoPair) - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); - else - { - /* Clamp Y, in case rounding errors caused it to end up outside - * of -1...+1. - */ - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - /* Negate Z for right-handed coords with -Z in front. */ - const ALfloat az{std::atan2(xpos, -zpos)}; + voice->mFlags.set(VoiceHasNfc); + } - /* A scalar of 1.5 for plain stereo results in +/-60 degrees - * being moved to +/-90 degrees for direct right and left - * speaker responses. - */ - CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs); - } + /* Panning a B-Format sound toward some direction is easy. Just pan the + * first (W) channel as a normal mono sound. The angular spread is used + * as a directional scalar to blend between full coverage and full + * panning. + */ + const float coverage{!(Distance > std::numeric_limits<float>::epsilon()) ? 1.0f : + (al::numbers::inv_pi_v<float>/2.0f * Spread)}; - /* NOTE: W needs to be scaled due to FuMa normalization. */ - const ALfloat &scale0 = AmbiScale::FromFuMa[0]; - ComputePanGains(&Device->Dry, coeffs, DryGain*scale0, + auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) + { + if(mode != RenderMode::Pairwise) + return CalcDirectionCoeffs({xpos, ypos, zpos}); + + /* Clamp Y, in case rounding errors caused it to end up outside + * of -1...+1. + */ + const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + /* Negate Z for right-handed coords with -Z in front. */ + const float az{std::atan2(xpos, -zpos)}; + + /* A scalar of 1.5 for plain stereo results in +/-60 degrees + * being moved to +/-90 degrees for direct right and left + * speaker responses. + */ + return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); + }; + auto&& scales = GetAmbiScales(voice->mAmbiScaling); + auto coeffs = calc_coeffs(Device->mRenderMode); + + if(!(coverage > 0.0f)) + { + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], voice->mChans[0].mDryParams.Gains.Target); - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i]*scale0, + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], voice->mChans[0].mWetParams[i].Gains.Target); } } else { - if(Device->AvgSpeakerDist > 0.0f) - { - /* NOTE: The NFCtrlFilters were created with a w0 of 0, which - * is what we want for FOA input. The first channel may have - * been previously re-adjusted if panned, so reset it. - */ - voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f); - - voice->mFlags |= VOICE_HAS_NFC; - } - /* Local B-Format sources have their XYZ channels rotated according * to the orientation. */ @@ -712,74 +886,159 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo V.normalize(); if(!props->HeadRelative) { - N = Listener.Params.Matrix * N; - V = Listener.Params.Matrix * V; + N = Context.Matrix * N; + V = Context.Matrix * V; } /* Build and normalize right-vector */ - alu::Vector U{aluCrossproduct(N, V)}; + alu::Vector U{N.cross_product(V)}; U.normalize(); - /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This - * matrix is transposed, for the inputs to align on the rows and - * outputs on the columns. + /* Build a rotation matrix. Manually fill the zeroth- and first- + * order elements, then construct the rotation for the higher + * orders. */ - const ALfloat &wscale = AmbiScale::FromFuMa[0]; - const ALfloat &yscale = AmbiScale::FromFuMa[1]; - const ALfloat &zscale = AmbiScale::FromFuMa[2]; - const ALfloat &xscale = AmbiScale::FromFuMa[3]; - const ALfloat matrix[4][MAX_AMBI_CHANNELS]{ - // ACN0 ACN1 ACN2 ACN3 - { wscale, 0.0f, 0.0f, 0.0f }, // FuMa W - { 0.0f, -N[0]*xscale, N[1]*xscale, -N[2]*xscale }, // FuMa X - { 0.0f, U[0]*yscale, -U[1]*yscale, U[2]*yscale }, // FuMa Y - { 0.0f, -V[0]*zscale, V[1]*zscale, -V[2]*zscale } // FuMa Z - }; + AmbiRotateMatrix &shrot = Device->mAmbiRotateMatrix; + shrot.fill(AmbiRotateMatrix::value_type{}); + + shrot[0][0] = 1.0f; + shrot[1][1] = U[0]; shrot[1][2] = -U[1]; shrot[1][3] = U[2]; + shrot[2][1] = -V[0]; shrot[2][2] = V[1]; shrot[2][3] = -V[2]; + shrot[3][1] = -N[0]; shrot[3][2] = N[1]; shrot[3][3] = -N[2]; + AmbiRotator(shrot, static_cast<int>(Device->mAmbiOrder)); + + /* If the device is higher order than the voice, "upsample" the + * matrix. + * + * NOTE: Starting with second-order, a 2D upsample needs to be + * applied with a 2D source and 3D output, even when they're the + * same order. This is because higher orders have a height offset + * on various channels (i.e. when elevation=0, those height-related + * channels should be non-0). + */ + AmbiRotateMatrix &mixmatrix = Device->mAmbiRotateMatrix2; + if(Device->mAmbiOrder > voice->mAmbiOrder + || (Device->mAmbiOrder >= 2 && !Device->m2DMixing + && Is2DAmbisonic(voice->mFmtChannels))) + { + if(voice->mAmbiOrder == 1) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 2) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 3) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 4) + { + auto&& upsampler = AmbiScale::FourthOrder2DUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else + al::unreachable(); + } + else + mixmatrix = shrot; + + /* Convert the rotation matrix for input ordering and scaling, and + * whether input is 2D or 3D. + */ + const uint8_t *index_map{Is2DAmbisonic(voice->mFmtChannels) ? + GetAmbi2DLayout(voice->mAmbiLayout).data() : + GetAmbiLayout(voice->mAmbiLayout).data()}; + + /* Scale the panned W signal inversely to coverage (full coverage + * means no panned signal), and according to the channel scaling. + */ + std::for_each(coeffs.begin(), coeffs.end(), + [scale=(1.0f-coverage)*scales[0]](float &coeff) noexcept { coeff *= scale; }); - for(ALuint c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { - ComputePanGains(&Device->Dry, matrix[c], DryGain, + const size_t acn{index_map[c]}; + const float scale{scales[acn] * coverage}; + + /* For channel 0, combine the B-Format signal (scaled according + * to the coverage amount) with the directional pan. For all + * other channels, use just the (scaled) B-Format signal. + */ + for(size_t x{0};x < MaxAmbiChannels;++x) + coeffs[x] += mixmatrix[acn][x] * scale; + + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, matrix[c], WetGain[i], + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } + + coeffs = std::array<float,MaxAmbiChannels>{}; } } } - else if(DirectChannels) + else if(DirectChannels != DirectMode::Off && !Device->RealOut.RemixMap.empty()) { /* Direct source channels always play local. Skip the virtual channels * and write inputs to the matching real outputs. */ voice->mDirect.Buffer = Device->RealOut.Buffer; - for(ALuint c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { - const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; + uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + else if(DirectChannels == DirectMode::RemixMismatch) + { + auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool + { return chans[c].channel == map.channel; }; + auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), + Device->RealOut.RemixMap.cend(), match_channel); + if(remap != Device->RealOut.RemixMap.cend()) + { + for(const auto &target : remap->targets) + { + idx = Device->channelIdxByName(target.channel); + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * + target.mix; + } + } + } } /* Auxiliary sends still use normal channel panning since they mix to * B-Format, which can't channel-match. */ - for(ALuint c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs); + /* Skip LFE */ + if(chans[c].channel == LFE) + continue; + + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } } - else if(Device->mRenderMode == HrtfRender) + else if(Device->mRenderMode == RenderMode::Hrtf) { /* Full HRTF rendering. Skip the virtual channels and render to the * real outputs. @@ -788,51 +1047,75 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo if(Distance > std::numeric_limits<float>::epsilon()) { - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const ALfloat az{std::atan2(xpos, -zpos)}; + const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; - /* Get the HRIR coefficients and delays just once, for the given - * source direction. - */ - GetHrtfCoeffs(Device->mHrtf, 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 * downmix_gain; + if(voice->mFmtChannels == FmtMono) + { + Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread, + voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, + voice->mChans[0].mDryParams.Hrtf.Target.Delay); + voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; - /* Remaining channels use the same results as the first. */ - for(ALuint c{1};c < num_channels;c++) + const auto coeffs = CalcAngleCoeffs(src_az, src_ev, Spread); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[0].mWetParams[i].Gains.Target); + } + } + else for(size_t c{0};c < num_channels;c++) { + using namespace al::numbers; + /* Skip LFE */ if(chans[c].channel == LFE) continue; - voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target; - } - /* Calculate the directional coefficients once, which apply to all - * input channels of the source sends. - */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); + /* Warp the channel position toward the source position as the + * source spread decreases. With no spread, all channels are at + * the source position, at full spread (pi*2), each channel is + * left unchanged. + */ + const float ev{lerpf(src_ev, chans[c].elevation, inv_pi_v<float>/2.0f * Spread)}; - for(ALuint c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; - for(ALuint i{0};i < NumSends;i++) + float az{chans[c].angle - src_az}; + if(az < -pi_v<float>) az += pi_v<float>*2.0f; + else if(az > pi_v<float>) az -= pi_v<float>*2.0f; + + az *= inv_pi_v<float>/2.0f * Spread; + + az += src_az; + if(az < -pi_v<float>) az += pi_v<float>*2.0f; + else if(az > pi_v<float>) az -= pi_v<float>*2.0f; + + Device->mHrtf->getCoeffs(ev, az, Distance*NfcScale, 0.0f, + voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, + voice->mChans[c].mDryParams.Hrtf.Target.Delay); + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; + + const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain, + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } } else { + /* With no distance, spread is only meaningful for mono sources + * where it can be 0 or full (non-mono sources are always full + * spread here). + */ + const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual * speaker" responses. */ - for(ALuint c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { /* Skip LFE */ if(chans[c].channel == LFE) @@ -841,26 +1124,25 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo /* Get the HRIR coefficients and delays for this channel * position. */ - GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle, - std::numeric_limits<float>::infinity(), Spread, + Device->mHrtf->getCoeffs(chans[c].elevation, chans[c].angle, + std::numeric_limits<float>::infinity(), spread, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain; + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; /* Normal panning for auxiliary sends. */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs); + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, spread); - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } } - voice->mFlags |= VOICE_HAS_HRTF; + voice->mFlags.set(VoiceHasHrtf); } else { @@ -874,50 +1156,88 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)}; + const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ - for(ALuint c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - voice->mFlags |= VOICE_HAS_NFC; + voice->mFlags.set(VoiceHasNfc); } - /* Calculate the directional coefficients once, which apply to all - * input channels. - */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - if(Device->mRenderMode != StereoPair) - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); - else + if(voice->mFmtChannels == FmtMono) { - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const ALfloat az{std::atan2(xpos, -zpos)}; - CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs); + auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) + { + if(mode != RenderMode::Pairwise) + return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); + const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float az{std::atan2(xpos, -zpos)}; + return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread); + }; + const auto coeffs = calc_coeffs(Device->mRenderMode); + + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[0].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[0].mWetParams[i].Gains.Target); + } } - - for(ALuint c{0};c < num_channels;c++) + else { - /* Special-case LFE */ - if(chans[c].channel == LFE) + using namespace al::numbers; + + const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; + + for(size_t c{0};c < num_channels;c++) { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) + /* Special-case LFE */ + if(chans[c].channel == LFE) { - const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) + { + const uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + } + continue; } - continue; - } - ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain, - voice->mChans[c].mDryParams.Gains.Target); - for(ALuint i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain, - voice->mChans[c].mWetParams[i].Gains.Target); + /* Warp the channel position toward the source position as + * the spread decreases. With no spread, all channels are + * at the source position, at full spread (pi*2), each + * channel position is left unchanged. + */ + const float ev{lerpf(src_ev, chans[c].elevation, + inv_pi_v<float>/2.0f * Spread)}; + + float az{chans[c].angle - src_az}; + if(az < -pi_v<float>) az += pi_v<float>*2.0f; + else if(az > pi_v<float>) az -= pi_v<float>*2.0f; + + az *= inv_pi_v<float>/2.0f * Spread; + + az += src_az; + if(az < -pi_v<float>) az += pi_v<float>*2.0f; + else if(az > pi_v<float>) az -= pi_v<float>*2.0f; + + if(Device->mRenderMode == RenderMode::Pairwise) + az = ScaleAzimuthFront(az, 3.0f); + const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } } @@ -925,46 +1245,45 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo { if(Device->AvgSpeakerDist > 0.0f) { - /* If the source distance is 0, set w0 to w1 to act as a pass- - * through. We still want to pass the signal through the - * filters so they keep an appropriate history, in case the - * source moves away from the listener. + /* If the source distance is 0, simulate a plane-wave by using + * infinite distance, which results in a w0 of 0. */ - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)}; - - for(ALuint c{0};c < num_channels;c++) + static constexpr float w0{0.0f}; + for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - voice->mFlags |= VOICE_HAS_NFC; + voice->mFlags.set(VoiceHasNfc); } - for(ALuint c{0};c < num_channels;c++) + /* With no distance, spread is only meaningful for mono sources + * where it can be 0 or full (non-mono sources are always full + * spread here). + */ + const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + for(size_t c{0};c < num_channels;c++) { /* Special-case LFE */ if(chans[c].channel == LFE) { if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; + const uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; } continue; } - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs( - (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f) - : chans[c].angle, - chans[c].elevation, Spread, coeffs - ); + const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise) + ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle, + chans[c].elevation, spread); - ComputePanGains(&Device->Dry, coeffs, DryGain, + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -972,44 +1291,37 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo } { - const ALfloat hfScale{props->Direct.HFReference / Frequency}; - const ALfloat lfScale{props->Direct.LFReference / Frequency}; - const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */ - const ALfloat gainLF{maxf(DryGainLF, 0.001f)}; + const float hfNorm{props->Direct.HFReference / Frequency}; + const float lfNorm{props->Direct.LFReference / Frequency}; voice->mDirect.FilterType = AF_None; - if(gainHF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; - if(gainLF != 1.0f) voice->mDirect.FilterType |= AF_HighPass; + if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; + if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass; + auto &lowpass = voice->mChans[0].mDryParams.LowPass; auto &highpass = voice->mChans[0].mDryParams.HighPass; - lowpass.setParams(BiquadType::HighShelf, gainHF, hfScale, - lowpass.rcpQFromSlope(gainHF, 1.0f)); - highpass.setParams(BiquadType::LowShelf, gainLF, lfScale, - highpass.rcpQFromSlope(gainLF, 1.0f)); - for(ALuint c{1};c < num_channels;c++) + lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f); + highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f); + for(size_t c{1};c < num_channels;c++) { voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass); voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass); } } - for(ALuint i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - const ALfloat hfScale{props->Send[i].HFReference / Frequency}; - const ALfloat lfScale{props->Send[i].LFReference / Frequency}; - const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)}; - const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)}; + const float hfNorm{props->Send[i].HFReference / Frequency}; + const float lfNorm{props->Send[i].LFReference / Frequency}; voice->mSend[i].FilterType = AF_None; - if(gainHF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass; - if(gainLF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass; + if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass; + if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass; auto &lowpass = voice->mChans[0].mWetParams[i].LowPass; auto &highpass = voice->mChans[0].mWetParams[i].HighPass; - lowpass.setParams(BiquadType::HighShelf, gainHF, hfScale, - lowpass.rcpQFromSlope(gainHF, 1.0f)); - highpass.setParams(BiquadType::LowShelf, gainLF, lfScale, - highpass.rcpQFromSlope(gainLF, 1.0f)); - for(ALuint c{1};c < num_channels;c++) + lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f); + highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f); + for(size_t c{1};c < num_channels;c++) { voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass); voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass); @@ -1017,18 +1329,16 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo } } -void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) +void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{ALContext->mDevice.get()}; - ALeffectslot *SendSlots[MAX_SENDS]; + DeviceBase *Device{context->mDevice}; + EffectSlot *SendSlots[MAX_SENDS]; voice->mDirect.Buffer = Device->Dry.Buffer; - for(ALuint i{0};i < Device->NumAuxSends;i++) + for(uint i{0};i < Device->NumAuxSends;i++) { SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->mDefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) + if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) { SendSlots[i] = nullptr; voice->mSend[i].Buffer = {}; @@ -1038,93 +1348,53 @@ void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, cons } /* Calculate the stepping value */ - const auto Pitch = static_cast<ALfloat>(voice->mFrequency) / - static_cast<ALfloat>(Device->Frequency) * props->Pitch; - if(Pitch > float{MAX_PITCH}) - voice->mStep = MAX_PITCH<<FRACTIONBITS; + const auto Pitch = static_cast<float>(voice->mFrequency) / + static_cast<float>(Device->Frequency) * props->Pitch; + if(Pitch > float{MaxPitch}) + voice->mStep = MaxPitch<<MixerFracBits; else - voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1); + voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ - const ALlistener &Listener = ALContext->mListener; - ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)}; - DryGain *= props->Direct.Gain * Listener.Params.Gain; - DryGain = minf(DryGain, GAIN_MIX_MAX); - ALfloat DryGainHF{props->Direct.GainHF}; - ALfloat DryGainLF{props->Direct.GainLF}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALuint i{0};i < Device->NumAuxSends;i++) + GainTriplet DryGain; + DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * + context->mParams.Gain, GainMixMax); + DryGain.HF = props->Direct.GainHF; + DryGain.LF = props->Direct.GainLF; + GainTriplet WetGain[MAX_SENDS]; + for(uint i{0};i < Device->NumAuxSends;i++) { - WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain); - WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain; - WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX); - WetGainHF[i] = props->Send[i].GainHF; - WetGainLF[i] = props->Send[i].GainLF; + WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * + props->Send[i].Gain * context->mParams.Gain, GainMixMax); + WetGain[i].HF = props->Send[i].GainHF; + WetGain[i].LF = props->Send[i].GainLF; } - CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, - WetGain, WetGainLF, WetGainHF, SendSlots, props, Listener, Device); + CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props, + context->mParams, Device); } -void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) +void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{ALContext->mDevice.get()}; - const ALuint NumSends{Device->NumAuxSends}; - const ALlistener &Listener = ALContext->mListener; + DeviceBase *Device{context->mDevice}; + const uint NumSends{Device->NumAuxSends}; /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; - ALeffectslot *SendSlots[MAX_SENDS]; - ALfloat RoomRolloff[MAX_SENDS]; - ALfloat DecayDistance[MAX_SENDS]; - ALfloat DecayLFDistance[MAX_SENDS]; - ALfloat DecayHFDistance[MAX_SENDS]; - for(ALuint i{0};i < NumSends;i++) + EffectSlot *SendSlots[MAX_SENDS]; + uint UseDryAttnForRoom{0}; + for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->mDefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) - { + if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) SendSlots[i] = nullptr; - RoomRolloff[i] = 0.0f; - DecayDistance[i] = 0.0f; - DecayLFDistance[i] = 0.0f; - DecayHFDistance[i] = 0.0f; - } - else if(SendSlots[i]->Params.AuxSendAuto) - { - RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance[i] = SendSlots[i]->Params.DecayTime * SPEEDOFSOUNDMETRESPERSEC; - DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio; - DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio; - if(SendSlots[i]->Params.DecayHFLimit) - { - ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)}; - DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]); - } - } - } - else + else if(!SendSlots[i]->AuxSendAuto) { /* If the slot's auxiliary send auto is off, the data sent to the - * effect slot is the same as the dry path, sans filter effects */ - RoomRolloff[i] = props->RolloffFactor; - DecayDistance[i] = 0.0f; - DecayLFDistance[i] = 0.0f; - DecayHFDistance[i] = 0.0f; + * effect slot is the same as the dry path, sans filter effects. + */ + UseDryAttnForRoom |= 1u<<i; } if(!SendSlots[i]) @@ -1137,208 +1407,228 @@ void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const A alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f}; alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f}; - if(props->HeadRelative == AL_FALSE) + if(!props->HeadRelative) { /* Transform source vectors */ - Position = Listener.Params.Matrix * Position; - Velocity = Listener.Params.Matrix * Velocity; - Direction = Listener.Params.Matrix * Direction; + Position = context->mParams.Matrix * (Position - context->mParams.Position); + Velocity = context->mParams.Matrix * Velocity; + Direction = context->mParams.Matrix * Direction; } else { /* Offset the source velocity to be relative of the listener velocity */ - Velocity += Listener.Params.Velocity; + Velocity += context->mParams.Velocity; } const bool directional{Direction.normalize() > 0.0f}; alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f}; - const ALfloat Distance{ToSource.normalize()}; - - /* Initial source gain */ - ALfloat DryGain{props->Gain}; - ALfloat DryGainHF{1.0f}; - ALfloat DryGainLF{1.0f}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALuint i{0};i < NumSends;i++) - { - WetGain[i] = props->Gain; - WetGainHF[i] = 1.0f; - WetGainLF[i] = 1.0f; - } + const float Distance{ToSource.normalize()}; /* Calculate distance attenuation */ - ALfloat ClampedDist{Distance}; + float ClampedDist{Distance}; + float DryGainBase{props->Gain}; + float WetGainBase{props->Gain}; - switch(Listener.Params.SourceDistanceModel ? - props->mDistanceModel : Listener.Params.mDistanceModel) + switch(context->mParams.SourceDistanceModel ? props->mDistanceModel + : context->mParams.mDistanceModel) { case DistanceModel::InverseClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Inverse: - if(!(props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(props->RefDistance > 0.0f) { - ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor); - if(dist > 0.0f) DryGain *= props->RefDistance / dist; - for(ALuint i{0};i < NumSends;i++) - { - dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]); - if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist; - } + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; + + dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); + if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; } break; case DistanceModel::LinearClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Linear: - if(!(props->MaxDistance != props->RefDistance)) - ClampedDist = props->RefDistance; - else + if(props->MaxDistance != props->RefDistance) { - ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - DryGain *= maxf(1.0f - attn, 0.0f); - for(ALuint i{0};i < NumSends;i++) - { - attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - WetGain[i] *= maxf(1.0f - attn, 0.0f); - } + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryGainBase *= maxf(1.0f - attn, 0.0f); + + attn = (ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; + WetGainBase *= maxf(1.0f - attn, 0.0f); } break; case DistanceModel::ExponentClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Exponent: - if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) { - DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor); - for(ALuint i{0};i < NumSends;i++) - WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]); + const float dist_ratio{ClampedDist/props->RefDistance}; + DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); + WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); } break; case DistanceModel::Disable: - ClampedDist = props->RefDistance; break; } /* Calculate directional soundcones */ + float ConeHF{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { - const ALfloat Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) * - ConeScale * 2.0f)}; + static constexpr float Rad2Deg{static_cast<float>(180.0 / al::numbers::pi)}; + const float Angle{Rad2Deg*2.0f * std::acos(-Direction.dot_product(ToSource)) * ConeScale}; - ALfloat ConeVolume, ConeHF; - if(!(Angle > props->InnerAngle)) + float ConeGain{1.0f}; + if(Angle >= props->OuterAngle) { - ConeVolume = 1.0f; - ConeHF = 1.0f; + ConeGain = props->OuterGain; + ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); } - else if(Angle < props->OuterAngle) + else if(Angle >= props->InnerAngle) { - ALfloat scale = ( Angle-props->InnerAngle) / - (props->OuterAngle-props->InnerAngle); - ConeVolume = lerp(1.0f, props->OuterGain, scale); - ConeHF = lerp(1.0f, props->OuterGainHF, scale); - } - else - { - ConeVolume = props->OuterGain; - ConeHF = props->OuterGainHF; + const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; + ConeGain = lerpf(1.0f, props->OuterGain, scale); + ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); } - DryGain *= ConeVolume; - if(props->DryGainHFAuto) - DryGainHF *= ConeHF; - if(props->WetGainAuto) - std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain), - [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; } - ); - if(props->WetGainHFAuto) - std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends, - std::begin(WetGainHF), - [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; } - ); + DryGainBase *= ConeGain; + WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); + + WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); } /* Apply gain and frequency filters */ - DryGain = clampf(DryGain, props->MinGain, props->MaxGain); - DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX); - DryGainHF *= props->Direct.GainHF; - DryGainLF *= props->Direct.GainLF; - for(ALuint i{0};i < NumSends;i++) + DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + + GainTriplet DryGain{}; + DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); + DryGain.HF = ConeHF * props->Direct.GainHF; + DryGain.LF = props->Direct.GainLF; + GainTriplet WetGain[MAX_SENDS]{}; + for(uint i{0};i < NumSends;i++) { - WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain); - WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX); - WetGainHF[i] *= props->Send[i].GainHF; - WetGainLF[i] *= props->Send[i].GainLF; + /* If this effect slot's Auxiliary Send Auto is off, then use the dry + * path distance and cone attenuation, otherwise use the wet (room) + * path distance and cone attenuation. The send filter is used instead + * of the direct filter, regardless. + */ + const bool use_room{!(UseDryAttnForRoom&(1u<<i))}; + const float gain{use_room ? WetGainBase : DryGainBase}; + WetGain[i].Base = minf(gain * props->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(Distance > props->RefDistance) LIKELY { - ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor * - Listener.Params.MetersPerUnit}; - if(props->AirAbsorptionFactor > 0.0f) + const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; + const float distance_meters{distance_base * context->mParams.MetersPerUnit}; + const float dryabsorb{distance_meters * props->AirAbsorptionFactor}; + if(dryabsorb > std::numeric_limits<float>::epsilon()) + DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb); + + /* If the source's Auxiliary Send Filter Gain Auto is off, no extra + * adjustment is applied to the send gains. + */ + for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i) { - ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)}; - DryGainHF *= hfattn; - std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends, - std::begin(WetGainHF), - [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; } - ); - } + if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) + continue; - if(props->WetGainAuto) - { - /* 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(ALuint i{0};i < NumSends;i++) + auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept { - if(!(DecayDistance[i] > 0.0f)) - continue; + const float dist{lerpf(refdist, distance, rolloff)}; + if(dist > refdist) return refdist / dist; + return 1.0f; + }; - const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])}; - WetGain[i] *= gain; - /* Yes, the wet path's air absorption is applied with - * WetGainAuto on, rather than WetGainHFAuto. - */ - if(gain > 0.0f) + /* The reverb effect's room rolloff factor always applies to an + * inverse distance rolloff model. + */ + WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, + SendSlots[i]->RoomRolloff); + + if(distance_meters > std::numeric_limits<float>::epsilon()) + WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); + + /* If this effect slot's Auxiliary Send Auto is off, don't apply + * the automatic initial reverb decay (should the reverb's room + * rolloff still apply?). + */ + if(!SendSlots[i]->AuxSendAuto) + continue; + + GainTriplet DecayDistance; + /* Calculate the distances to where this effect's decay reaches + * -60dB. + */ + DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; + DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; + DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; + if(SendSlots[i]->DecayHFLimit) + { + const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; + if(airAbsorption < 1.0f) { - ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])}; - WetGainHF[i] *= minf(gainhf / gain, 1.0f); - ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])}; - WetGainLF[i] *= minf(gainlf / gain, 1.0f); + /* Calculate the distance to where this effect's air + * absorption reaches -60dB, and limit the effect's HF + * decay distance (so it doesn't take any longer to decay + * than the air would allow). + */ + static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; + const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; + DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); } } + + const float baseAttn = calc_attenuation(Distance, props->RefDistance, + props->RolloffFactor); + + /* Apply a decay-time transformation to the wet path, based on the + * source distance. The initial decay of the reverb effect is + * calculated and applied to the wet path. + */ + const float fact{distance_base / DecayDistance.Base}; + const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].Base *= gain; + + if(gain > 0.0f) + { + const float hffact{distance_base / DecayDistance.HF}; + const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].HF *= minf(gainhf/gain, 1.0f); + const float lffact{distance_base / DecayDistance.LF}; + const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].LF *= minf(gainlf/gain, 1.0f); + } } } /* Initial source pitch */ - ALfloat Pitch{props->Pitch}; + float Pitch{props->Pitch}; /* Calculate velocity-based doppler effect */ - ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor}; + float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor}; if(DopplerFactor > 0.0f) { - const alu::Vector &lvelocity = Listener.Params.Velocity; - ALfloat vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor}; - ALfloat vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor}; + const alu::Vector &lvelocity = context->mParams.Velocity; + float vss{Velocity.dot_product(ToSource) * -DopplerFactor}; + float vls{lvelocity.dot_product(ToSource) * -DopplerFactor}; - const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound}; + const float SpeedOfSound{context->mParams.SpeedOfSound}; if(!(vls < SpeedOfSound)) { /* Listener moving away from the source at the speed of sound. @@ -1365,27 +1655,26 @@ void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const A /* Adjust pitch based on the buffer and output frequencies, and calculate * fixed-point stepping value. */ - Pitch *= static_cast<ALfloat>(voice->mFrequency)/static_cast<ALfloat>(Device->Frequency); - if(Pitch > float{MAX_PITCH}) - voice->mStep = MAX_PITCH<<FRACTIONBITS; + Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency); + if(Pitch > float{MaxPitch}) + voice->mStep = MaxPitch<<MixerFracBits; else - voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1); + voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); - ALfloat spread{0.0f}; + float spread{0.0f}; if(props->Radius > Distance) - spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi(); + spread = al::numbers::pi_v<float>*2.0f - Distance/props->Radius*al::numbers::pi_v<float>; else if(Distance > 0.0f) spread = std::asin(props->Radius/Distance) * 2.0f; - CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale, - Distance*Listener.Params.MetersPerUnit, spread, DryGain, DryGainHF, DryGainLF, WetGain, - WetGainLF, WetGainHF, SendSlots, props, Listener, Device); + CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale, + Distance, spread, DryGain, WetGain, SendSlots, props, context->mParams, Device); } -void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force) +void CalcSourceParams(Voice *voice, ContextBase *context, bool force) { - ALvoiceProps *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; + VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props && !force) return; if(props) @@ -1395,221 +1684,284 @@ void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force) AtomicReplaceHead(context->mFreeVoiceProps, props); } - if((voice->mProps.mSpatializeMode == SpatializeAuto && voice->mFmtChannels == FmtMono) || - voice->mProps.mSpatializeMode == SpatializeOn) - CalcAttnSourceParams(voice, &voice->mProps, context); - else + if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono + && !IsAmbisonic(voice->mFmtChannels)) + || voice->mProps.mSpatializeMode == SpatializeMode::Off + || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono)) CalcNonAttnSourceParams(voice, &voice->mProps, context); + else + CalcAttnSourceParams(voice, &voice->mProps, context); } -void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots, - const al::span<ALvoice> voices) +void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) { - IncrementRef(ctx->mUpdateCount); - if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire)) + RingBuffer *ring{context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len < 1) return; + + AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), + AsyncEvent::SourceStateChange)}; + evt->u.srcstate.id = id; + switch(state) { - bool force{CalcContextParams(ctx)}; - force |= CalcListenerParams(ctx); - force = std::accumulate(slots.begin(), slots.end(), force, - [ctx](const bool f, ALeffectslot *slot) -> bool - { return CalcEffectSlotParams(slot, ctx) | f; } - ); - - auto calc_params = [ctx,force](ALvoice &voice) -> void - { - if(voice.mSourceID.load(std::memory_order_acquire) != 0) - CalcSourceParams(&voice, ctx, force); - }; - std::for_each(voices.begin(), voices.end(), calc_params); + 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: + al::unreachable(); } - IncrementRef(ctx->mUpdateCount); + + ring->writeAdvance(1); } -void ProcessContext(ALCcontext *ctx, const ALuint SamplesToDo) +void ProcessVoiceChanges(ContextBase *ctx) { - ASSUME(SamplesToDo > 0); + VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; + VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}; + if(!next) return; - const ALeffectslotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); - const al::span<ALvoice> voices{ctx->mVoices.data(), ctx->mVoices.size()}; + const auto enabledevt = ctx->mEnabledEvts.load(std::memory_order_acquire); + do { + cur = next; - /* Process pending propery updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots, voices); - - /* Clear auxiliary effect slot mixing buffers. */ - std::for_each(auxslots.begin(), auxslots.end(), - [SamplesToDo](ALeffectslot *slot) -> void + bool sendevt{false}; + if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop) { - for(auto &buffer : slot->MixBuffer) - std::fill_n(buffer.begin(), SamplesToDo, 0.0f); + if(Voice *voice{cur->mVoice}) + { + voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + /* A source ID indicates the voice was playing or paused, which + * gets a reset/stop event. + */ + sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u; + Voice::State oldvstate{Voice::Playing}; + voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); + voice->mPendingChange.store(false, std::memory_order_release); + } + /* Reset state change events are always sent, even if the voice is + * already stopped or even if there is no voice. + */ + sendevt |= (cur->mState == VChangeState::Reset); } - ); - - /* Process voices that have a playing source. */ - std::for_each(voices.begin(), voices.end(), - [SamplesToDo,ctx](ALvoice &voice) -> void + else if(cur->mState == VChangeState::Pause) { - const ALvoice::State vstate{voice.mPlayState.load(std::memory_order_acquire)}; - if(vstate != ALvoice::Stopped) voice.mix(vstate, ctx, SamplesToDo); + Voice *voice{cur->mVoice}; + Voice::State oldvstate{Voice::Playing}; + sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_release, std::memory_order_acquire); } - ); + else if(cur->mState == VChangeState::Play) + { + /* NOTE: When playing a voice, sending a source state change event + * depends if there's an old voice to stop and if that stop is + * successful. If there is no old voice, a playing event is always + * sent. If there is an old voice, an event is sent only if the + * voice is already stopped. + */ + if(Voice *oldvoice{cur->mOldVoice}) + { + oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mSourceID.store(0u, std::memory_order_relaxed); + Voice::State oldvstate{Voice::Playing}; + sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); + oldvoice->mPendingChange.store(false, std::memory_order_release); + } + else + sendevt = true; - /* Process effects. */ - if(auxslots.empty()) return; - auto slots = auxslots.data(); - auto slots_end = slots + auxslots.size(); + Voice *voice{cur->mVoice}; + voice->mPlayState.store(Voice::Playing, std::memory_order_release); + } + else if(cur->mState == VChangeState::Restart) + { + /* Restarting a voice never sends a source change event. */ + Voice *oldvoice{cur->mOldVoice}; + oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + /* If there's no sourceID, the old voice finished so don't start + * the new one at its new offset. + */ + if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u) + { + /* Otherwise, set the voice to stopping if it's not already (it + * might already be, if paused), and play the new voice as + * appropriate. + */ + Voice::State oldvstate{Voice::Playing}; + oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); - /* First sort the slots into scratch storage, so that effects come before - * their effect target (or their targets' target). - */ - auto sorted_slots = const_cast<ALeffectslot**>(slots_end); - auto sorted_slots_end = sorted_slots; - auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool - { - while((slot1=slot1->Params.Target) != nullptr) { - if(slot1 == slot2) return true; + Voice *voice{cur->mVoice}; + voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing + : Voice::Stopped, std::memory_order_release); + } + oldvoice->mPendingChange.store(false, std::memory_order_release); } - return false; - }; + if(sendevt && enabledevt.test(AsyncEvent::SourceStateChange)) + SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); + + next = cur->mNext.load(std::memory_order_acquire); + } while(next); + ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); +} - *sorted_slots_end = *slots; - ++sorted_slots_end; - while(++slots != slots_end) +void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, + const al::span<Voice*> voices) +{ + ProcessVoiceChanges(ctx); + + IncrementRef(ctx->mUpdateCount); + if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { - /* If this effect slot targets an effect slot already in the list (i.e. - * slots outputs to something in sorted_slots), directly or indirectly, - * insert it prior to that element. - */ - auto checker = sorted_slots; - do { - if(in_chain(*slots, *checker)) break; - } while(++checker != sorted_slots_end); - - checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1); - *--checker = *slots; - ++sorted_slots_end; - } + bool force{CalcContextParams(ctx)}; + auto sorted_slots = const_cast<EffectSlot**>(slots.data() + slots.size()); + for(EffectSlot *slot : slots) + force |= CalcEffectSlotParams(slot, sorted_slots, ctx); - std::for_each(sorted_slots, sorted_slots_end, - [SamplesToDo](const ALeffectslot *slot) -> void + for(Voice *voice : voices) { - EffectState *state{slot->Params.mEffectState}; - state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); + /* Only update voices that have a source. */ + if(voice->mSourceID.load(std::memory_order_relaxed) != 0) + CalcSourceParams(voice, ctx, force); } - ); + } + IncrementRef(ctx->mUpdateCount); } - -void ApplyStablizer(FrontStablizer *Stablizer, const al::span<FloatBufferLine> Buffer, - const ALuint lidx, const ALuint ridx, const ALuint cidx, const ALuint SamplesToDo) +void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - /* Apply a delay to all channels, except the front-left and front-right, so - * they maintain correct timing. - */ - const size_t NumChannels{Buffer.size()}; - for(size_t i{0u};i < NumChannels;i++) + const nanoseconds curtime{device->ClockBase + + nanoseconds{seconds{device->SamplesDone}}/device->Frequency}; + + for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { - if(i == lidx || i == ridx) - continue; + const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); + const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()}; - auto &DelayBuf = Stablizer->DelayBuf[i]; - auto buffer_end = Buffer[i].begin() + SamplesToDo; - if LIKELY(SamplesToDo >= ALuint{FrontStablizer::DelayLength}) + /* Process pending propery updates for objects on the context. */ + ProcessParamUpdates(ctx, auxslots, voices); + + /* Clear auxiliary effect slot mixing buffers. */ + for(EffectSlot *slot : auxslots) { - auto delay_end = std::rotate(Buffer[i].begin(), - buffer_end - FrontStablizer::DelayLength, buffer_end); - std::swap_ranges(Buffer[i].begin(), delay_end, std::begin(DelayBuf)); + for(auto &buffer : slot->Wet.Buffer) + buffer.fill(0.0f); } - else + + /* Process voices that have a playing source. */ + for(Voice *voice : voices) { - auto delay_start = std::swap_ranges(Buffer[i].begin(), buffer_end, - std::begin(DelayBuf)); - std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf)); + const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; + if(vstate != Voice::Stopped && vstate != Voice::Pending) + voice->mix(vstate, ctx, curtime, SamplesToDo); } - } - ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit; - ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit; - auto &tmpbuf = Stablizer->TempBuf; - - /* This applies the band-splitter, preserving phase at the cost of some - * delay. The shorter the delay, the more error seeps into the result. - */ - auto apply_splitter = [&tmpbuf,SamplesToDo](const FloatBufferLine &InBuf, - ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter, - ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void - { - /* Combine the delayed samples and the input samples into the temp - * buffer, in reverse. Then copy the final samples back into the delay - * buffer for next time. Note that the delay buffer's samples are - * stored backwards here. - */ - auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo; - std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end); - std::reverse_copy(InBuf.begin(), InBuf.begin()+SamplesToDo, std::begin(tmpbuf)); - std::copy_n(std::begin(tmpbuf), FrontStablizer::DelayLength, std::begin(DelayBuf)); - - /* Apply an all-pass on the reversed signal, then reverse the samples - * to get the forward signal with a reversed phase shift. - */ - Filter.applyAllpass(tmpbuf, SamplesToDo+FrontStablizer::DelayLength); - std::reverse(std::begin(tmpbuf), tmpbuf_end+FrontStablizer::DelayLength); + /* Process effects. */ + if(const size_t num_slots{auxslots.size()}) + { + auto slots = auxslots.data(); + auto slots_end = slots + num_slots; - /* Now apply the band-splitter, combining its phase shift with the - * reversed phase shift, restoring the original phase on the split - * signal. - */ - Filter.process(splitbuf[1], splitbuf[0], tmpbuf, SamplesToDo); - }; - apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit); - apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit); + /* Sort the slots into extra storage, so that effect slots come + * before their effect slot target (or their targets' target). + */ + const al::span<EffectSlot*> sorted_slots{const_cast<EffectSlot**>(slots_end), + num_slots}; + /* Skip sorting if it has already been done. */ + if(!sorted_slots[0]) + { + /* First, copy the slots to the sorted list, then partition the + * sorted list so that all slots without a target slot go to + * the end. + */ + std::copy(slots, slots_end, sorted_slots.begin()); + auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), + [](const EffectSlot *slot) noexcept -> bool + { return slot->Target != nullptr; }); + /* There must be at least one slot without a slot target. */ + assert(split_point != sorted_slots.end()); + + /* Simple case: no more than 1 slot has a target slot. Either + * all slots go right to the output, or the remaining one must + * target an already-partitioned slot. + */ + if(split_point - sorted_slots.begin() > 1) + { + /* At least two slots target other slots. Starting from the + * back of the sorted list, continue partitioning the front + * of the list given each target until all targets are + * accounted for. This ensures all slots without a target + * go last, all slots directly targeting those last slots + * go second-to-last, all slots directly targeting those + * second-last slots go third-to-last, etc. + */ + auto next_target = sorted_slots.end(); + do { + /* This shouldn't happen, but if there's unsorted slots + * left that don't target any sorted slots, they can't + * contribute to the output, so leave them. + */ + if(next_target == split_point) UNLIKELY + break; + + --next_target; + split_point = std::partition(sorted_slots.begin(), split_point, + [next_target](const EffectSlot *slot) noexcept -> bool + { return slot->Target != *next_target; }); + } while(split_point - sorted_slots.begin() > 1); + } + } - for(ALuint i{0};i < SamplesToDo;i++) - { - ALfloat lfsum{lsplit[0][i] + rsplit[0][i]}; - ALfloat hfsum{lsplit[1][i] + rsplit[1][i]}; - ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]}; - - /* This pans the separate low- and high-frequency sums between being on - * the center channel and the left/right channels. The low-frequency - * sum is 1/3rd toward center (2/3rds on left/right) and the high- - * frequency sum is 1/4th toward center (3/4ths on left/right). These - * values can be tweaked. - */ - ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) + - hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))}; - ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) + - hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))}; + for(const EffectSlot *slot : sorted_slots) + { + EffectState *state{slot->mEffectState.get()}; + state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); + } + } - /* The generated center channel signal adds to the existing signal, - * while the modified left and right channels replace. - */ - Buffer[lidx][i] = (m + s) * 0.5f; - Buffer[ridx][i] = (m - s) * 0.5f; - Buffer[cidx][i] += c * 0.5f; + /* Signal the event handler if there are any events to read. */ + RingBuffer *ring{ctx->mAsyncEvents.get()}; + if(ring->readSpace() > 0) + ctx->mEventSem.post(); } } -void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALuint SamplesToDo, - const DistanceComp::DistData *distcomp) + +void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t SamplesToDo, + const DistanceComp::ChanData *distcomp) { ASSUME(SamplesToDo > 0); for(auto &chanbuffer : Samples) { - const ALfloat gain{distcomp->Gain}; - const ALuint base{distcomp->Length}; - ALfloat *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; + const float gain{distcomp->Gain}; + const size_t base{distcomp->Length}; + float *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; ++distcomp; if(base < 1) continue; - ALfloat *inout{al::assume_aligned<16>(chanbuffer.data())}; + float *inout{al::assume_aligned<16>(chanbuffer.data())}; auto inout_end = inout + SamplesToDo; - if LIKELY(SamplesToDo >= base) + if(SamplesToDo >= base) LIKELY { auto delay_end = std::rotate(inout, inout_end - base, inout_end); std::swap_ranges(inout, delay_end, distbuf); @@ -1619,33 +1971,31 @@ void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALuint Sam auto delay_start = std::swap_ranges(inout, inout_end, distbuf); std::rotate(distbuf, delay_start, distbuf + base); } - std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain)); + std::transform(inout, inout_end, inout, [gain](float s) { return s * gain; }); } } -void ApplyDither(const al::span<FloatBufferLine> Samples, ALuint *dither_seed, - const ALfloat quant_scale, const ALuint SamplesToDo) +void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed, + const float quant_scale, const size_t SamplesToDo) { + ASSUME(SamplesToDo > 0); + /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to * the desired quantization depth amd before rounding. */ - const ALfloat invscale{1.0f / quant_scale}; - ALuint seed{*dither_seed}; - auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](FloatBufferLine &input) -> void + const float invscale{1.0f / quant_scale}; + uint seed{*dither_seed}; + auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float { - ASSUME(SamplesToDo > 0); - auto dither_sample = [&seed,invscale,quant_scale](const ALfloat sample) noexcept -> ALfloat - { - ALfloat val{sample * quant_scale}; - ALuint rng0{dither_rng(&seed)}; - ALuint rng1{dither_rng(&seed)}; - val += static_cast<ALfloat>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); - return fast_roundf(val) * invscale; - }; - std::transform(input.begin(), input.begin()+SamplesToDo, input.begin(), dither_sample); + float val{sample * quant_scale}; + uint rng0{dither_rng(&seed)}; + uint rng1{dither_rng(&seed)}; + val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); + return fast_roundf(val) * invscale; }; - std::for_each(Samples.begin(), Samples.end(), dither_channel); + for(FloatBufferLine &inout : Samples) + std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample); *dither_seed = seed; } @@ -1682,161 +2032,179 @@ template<> inline uint8_t SampleConv(float val) noexcept template<DevFmtType T> void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset, - const ALuint SamplesToDo) + const size_t SamplesToDo, const size_t FrameStep) { - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const size_t numchans{InBuffer.size()}; - ASSUME(numchans > 0); + ASSUME(FrameStep > 0); + ASSUME(SamplesToDo > 0); - SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans; - auto conv_channel = [&outbase,SamplesToDo,numchans](const FloatBufferLine &inbuf) -> void + DevFmtType_t<T> *outbase{static_cast<DevFmtType_t<T>*>(OutBuffer) + Offset*FrameStep}; + size_t c{0}; + for(const FloatBufferLine &inbuf : InBuffer) { - ASSUME(SamplesToDo > 0); - SampleType *out{outbase++}; - auto conv_sample = [numchans,&out](const float s) noexcept -> void + DevFmtType_t<T> *out{outbase++}; + auto conv_sample = [FrameStep,&out](const float s) noexcept -> void { - *out = SampleConv<SampleType>(s); - out += numchans; + *out = SampleConv<DevFmtType_t<T>>(s); + out += FrameStep; }; std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); - }; - std::for_each(InBuffer.cbegin(), InBuffer.cend(), conv_channel); + ++c; + } + if(const size_t extra{FrameStep - c}) + { + const auto silence = SampleConv<DevFmtType_t<T>>(0.0f); + for(size_t i{0};i < SamplesToDo;++i) + { + std::fill_n(outbase, extra, silence); + outbase += FrameStep; + } + } } } // namespace -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, const ALuint NumSamples) +uint DeviceBase::renderSamples(const uint numSamples) { - FPUCtl mixer_mode{}; - for(ALuint SamplesDone{0u};SamplesDone < NumSamples;) - { - const ALuint SamplesToDo{minu(NumSamples-SamplesDone, BUFFERSIZE)}; + const uint samplesToDo{minu(numSamples, BufferLineSize)}; - /* Clear main mixing buffers. */ - std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(), - [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void - { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); } - ); + /* 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(device->MixCount); + /* Increment the mix count at the start (lsb should now be 1). */ + IncrementRef(MixCount); - /* For each context on this device, process and mix its sources and - * effects. - */ - for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire)) - ProcessContext(ctx, 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. - */ - device->SamplesDone += SamplesToDo; - device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency}; - device->SamplesDone %= device->Frequency; + /* Increment the clock time. Every second's worth of samples is converted + * and added to clock base so that large sample counts don't overflow + * during conversion. This also guarantees a stable conversion. + */ + SamplesDone += samplesToDo; + ClockBase += std::chrono::seconds{SamplesDone / Frequency}; + SamplesDone %= Frequency; - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(device->MixCount); + /* Increment the mix count at the end (lsb should now be 0). */ + IncrementRef(MixCount); - /* Apply any needed post-process for finalizing the Dry mix to the - * RealOut (Ambisonic decode, UHJ encode, etc). - */ - device->postProcess(SamplesToDo); + /* Apply any needed post-process for finalizing the Dry mix to the RealOut + * (Ambisonic decode, UHJ encode, etc). + */ + postProcess(samplesToDo); - const al::span<FloatBufferLine> RealOut{device->RealOut.Buffer}; + /* Apply compression, limiting sample amplitude if needed or desired. */ + if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); - /* Apply front image stablization for surround sound, if applicable. */ - if(device->Stablizer) - { - const ALuint lidx{GetChannelIdxByName(device->RealOut, FrontLeft)}; - const ALuint ridx{GetChannelIdxByName(device->RealOut, FrontRight)}; - const ALuint cidx{GetChannelIdxByName(device->RealOut, FrontCenter)}; + /* Apply delays and attenuation for mismatched speaker distances. */ + if(ChannelDelays) + ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); - ApplyStablizer(device->Stablizer.get(), RealOut, lidx, ridx, cidx, SamplesToDo); - } + /* Apply dithering. The compressor should have left enough headroom for the + * dither noise to not saturate. + */ + if(DitherDepth > 0.0f) + ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo); - /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Compressor *comp{device->Limiter.get()}) - comp->process(SamplesToDo, RealOut.data()); + return samplesToDo; +} - /* Apply delays and attenuation for mismatched speaker distances. */ - ApplyDistanceComp(RealOut, SamplesToDo, device->ChannelDelay.as_span().cbegin()); +void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples) +{ + FPUCtl mixer_mode{}; + uint total{0}; + while(const uint todo{numSamples - total}) + { + const uint samplesToDo{renderSamples(todo)}; - /* Apply dithering. The compressor should have left enough headroom for - * the dither noise to not saturate. - */ - if(device->DitherDepth > 0.0f) - ApplyDither(RealOut, &device->DitherSeed, device->DitherDepth, SamplesToDo); + auto *srcbuf = RealOut.Buffer.data(); + for(auto *dstbuf : outBuffers) + { + std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total); + ++srcbuf; + } - if LIKELY(OutBuffer) + total += samplesToDo; + } +} + +void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) +{ + FPUCtl mixer_mode{}; + uint total{0}; + while(const uint todo{numSamples - total}) + { + const uint samplesToDo{renderSamples(todo)}; + + if(outBuffer) LIKELY { /* Finally, interleave and convert samples, writing to the device's * output buffer. */ - switch(device->FmtType) + switch(FmtType) { -#define HANDLE_WRITE(T) case T: \ - Write<T>(RealOut, OutBuffer, SamplesDone, SamplesToDo); break; - HANDLE_WRITE(DevFmtByte) - HANDLE_WRITE(DevFmtUByte) - HANDLE_WRITE(DevFmtShort) - HANDLE_WRITE(DevFmtUShort) - HANDLE_WRITE(DevFmtInt) - HANDLE_WRITE(DevFmtUInt) - HANDLE_WRITE(DevFmtFloat) +#define HANDLE_WRITE(T) case T: \ + Write<T>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; + HANDLE_WRITE(DevFmtByte) + HANDLE_WRITE(DevFmtUByte) + HANDLE_WRITE(DevFmtShort) + HANDLE_WRITE(DevFmtUShort) + HANDLE_WRITE(DevFmtInt) + HANDLE_WRITE(DevFmtUInt) + HANDLE_WRITE(DevFmtFloat) #undef HANDLE_WRITE } } - SamplesDone += SamplesToDo; + total += samplesToDo; } } - -void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) +void DeviceBase::handleDisconnect(const char *msg, ...) { - if(!device->Connected.exchange(false, std::memory_order_acq_rel)) - return; - - AsyncEvent evt{EventType_Disconnected}; - evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT; - evt.u.user.id = 0; - evt.u.user.param = 0; + IncrementRef(MixCount); + if(Connected.exchange(false, std::memory_order_acq_rel)) + { + AsyncEvent evt{AsyncEvent::Disconnected}; - va_list args; - va_start(args, msg); - int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)}; - va_end(args); + va_list args; + va_start(args, msg); + int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)}; + va_end(args); - if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg)) - evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0; + if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.disconnect.msg)) + evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; - IncrementRef(device->MixCount); - for(ALCcontext *ctx : *device->mContexts.load()) - { - const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; - if((enabledevt&EventType_Disconnected)) + for(ContextBase *ctx : *mContexts.load()) { - RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; - if(evt_data.len > 0) + if(ctx->mEnabledEvts.load(std::memory_order_acquire).test(AsyncEvent::Disconnected)) { - ::new (evt_data.buf) AsyncEvent{evt}; - ring->writeAdvance(1); - ctx->mEventSem.post(); + RingBuffer *ring{ctx->mAsyncEvents.get()}; + auto evt_data = ring->getWriteVector().first; + if(evt_data.len > 0) + { + al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt); + ring->writeAdvance(1); + ctx->mEventSem.post(); + } } - } - auto stop_voice = [](ALvoice &voice) -> void - { - voice.mCurrentBuffer.store(nullptr, std::memory_order_relaxed); - voice.mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice.mSourceID.store(0u, std::memory_order_relaxed); - voice.mPlayState.store(ALvoice::Stopped, std::memory_order_release); - }; - std::for_each(ctx->mVoices.begin(), ctx->mVoices.end(), stop_voice); + if(!ctx->mStopVoicesOnDisconnect) + { + ProcessVoiceChanges(ctx); + continue; + } + + auto voicelist = ctx->getVoicesSpanAcquired(); + auto stop_voice = [](Voice *voice) -> void + { + voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + voice->mSourceID.store(0u, std::memory_order_relaxed); + voice->mPlayState.store(Voice::Stopped, std::memory_order_release); + }; + std::for_each(voicelist.begin(), voicelist.end(), stop_voice); + } } - IncrementRef(device->MixCount); + IncrementRef(MixCount); } @@ -1,158 +1,38 @@ #ifndef ALU_H #define ALU_H -#include <array> -#include <cmath> -#include <cstddef> +#include <bitset> -#include "AL/al.h" +#include "aloptional.h" -#include "alcmain.h" -#include "alspan.h" -#include "logging.h" +struct ALCcontext; +struct ALCdevice; +struct EffectSlot; -struct ALbufferlistitem; -struct ALeffectslot; +enum class StereoEncoding : unsigned char; -#define MAX_PITCH 255 -#define MAX_SENDS 16 +constexpr float GainMixMax{1000.0f}; /* +60dB */ -using MixerFunc = void(*)(const al::span<const float> InSamples, - const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos); -using RowMixerFunc = void(*)(const al::span<float> OutBuffer, const al::span<const float> Gains, - const float *InSamples, const size_t InStride); -using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const size_t BufferSize); +enum CompatFlags : uint8_t { + ReverseX, + ReverseY, + ReverseZ, -extern MixerFunc MixSamples; -extern RowMixerFunc MixRowSamples; - - -#define GAIN_MIX_MAX (1000.0f) /* +60dB */ - -#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */ - -#define SPEEDOFSOUNDMETRESPERSEC (343.3f) -#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */ - -/* Target gain for the reverb decay feedback reaching the decay time. */ -#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */ - -#define FRACTIONBITS (12) -#define FRACTIONONE (1<<FRACTIONBITS) -#define FRACTIONMASK (FRACTIONONE-1) - - -inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu) noexcept -{ return val1 + (val2-val1)*mu; } -inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu) noexcept -{ - ALfloat mu2 = mu*mu, mu3 = mu2*mu; - ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu; - ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f; - ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu; - ALfloat a3 = 0.5f*mu3 + -0.5f*mu2; - return val1*a0 + val2*a1 + val3*a2 + val4*a3; -} - - -enum HrtfRequestMode { - Hrtf_Default = 0, - Hrtf_Enable = 1, - Hrtf_Disable = 2, + Count }; +using CompatFlagBitset = std::bitset<CompatFlags::Count>; -void aluInit(void); - -void aluInitMixer(void); +void aluInit(CompatFlagBitset flags, const float nfcscale); /* aluInitRenderer * * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq); - -void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device); - -/** - * Calculates ambisonic encoder coefficients using the X, Y, and Z direction - * components, which must represent a normalized (unit length) vector, and the - * spread is the angular width of the sound (0...tau). - * - * NOTE: The components use ambisonic coordinates. As a result: - * - * Ambisonic Y = OpenAL -X - * Ambisonic Z = OpenAL Y - * Ambisonic X = OpenAL -Z - * - * The components are ordered such that OpenAL's X, Y, and Z are the first, - * second, and third parameters respectively -- simply negate X and Z. - */ -void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread, - const al::span<float,MAX_AMBI_CHANNELS> coeffs); - -/** - * CalcDirectionCoeffs - * - * Calculates ambisonic coefficients based on an OpenAL direction vector. The - * vector must be normalized (unit length), and the spread is the angular width - * of the sound (0...tau). - */ -inline void CalcDirectionCoeffs(const float (&dir)[3], const float spread, - const al::span<float,MAX_AMBI_CHANNELS> coeffs) -{ - /* Convert from OpenAL coords to Ambisonics. */ - CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs); -} - -/** - * CalcAngleCoeffs - * - * Calculates ambisonic coefficients based on azimuth and elevation. The - * azimuth and elevation parameters are in radians, going right and up - * respectively. - */ -inline void CalcAngleCoeffs(const float azimuth, const float elevation, const float spread, - const al::span<float,MAX_AMBI_CHANNELS> coeffs) -{ - const float x{-std::sin(azimuth) * std::cos(elevation)}; - const float y{ std::sin(elevation)}; - const float z{ std::cos(azimuth) * std::cos(elevation)}; - - CalcAmbiCoeffs(x, y, z, spread, coeffs); -} - - -/** - * ComputePanGains - * - * Computes panning gains using the given channel decoder coefficients and the - * pre-calculated direction or angle coefficients. For B-Format sources, the - * coeffs are a 'slice' of a transform matrix for the input channel, used to - * scale and orient the sound samples. - */ -void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span<float,MAX_OUTPUT_CHANNELS> gains); - - -inline std::array<ALfloat,MAX_AMBI_CHANNELS> GetAmbiIdentityRow(size_t i) noexcept -{ - std::array<ALfloat,MAX_AMBI_CHANNELS> ret{}; - ret[i] = 1.0f; - return ret; -} - - -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, const ALuint NumSamples); -/* Caller must lock the device state, and the mixer must not be running. */ -void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3); +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode); -extern const ALfloat ConeScale; -extern const ALfloat ZScale; +void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); #endif diff --git a/alc/ambdec.cpp b/alc/ambdec.cpp deleted file mode 100644 index adf116fe..00000000 --- a/alc/ambdec.cpp +++ /dev/null @@ -1,434 +0,0 @@ - -#include "config.h" - -#include "ambdec.h" - -#include <algorithm> -#include <cctype> -#include <cstddef> -#include <iterator> -#include <sstream> -#include <string> - -#include "alfstream.h" -#include "logging.h" - - -namespace { - -template<typename T, std::size_t N> -constexpr inline std::size_t size(const T(&)[N]) noexcept -{ return N; } - -int readline(std::istream &f, std::string &output) -{ - while(f.good() && f.peek() == '\n') - f.ignore(); - - return std::getline(f, output) && !output.empty(); -} - -bool read_clipped_line(std::istream &f, std::string &buffer) -{ - while(readline(f, buffer)) - { - std::size_t pos{0}; - while(pos < buffer.length() && std::isspace(buffer[pos])) - pos++; - buffer.erase(0, pos); - - std::size_t cmtpos{buffer.find_first_of('#')}; - if(cmtpos < buffer.length()) - buffer.resize(cmtpos); - while(!buffer.empty() && std::isspace(buffer.back())) - buffer.pop_back(); - - if(!buffer.empty()) - return true; - } - return false; -} - - -std::string read_word(std::istream &f) -{ - std::string ret; - f >> ret; - return ret; -} - -bool is_at_end(const std::string &buffer, std::size_t endpos) -{ - while(endpos < buffer.length() && std::isspace(buffer[endpos])) - ++endpos; - return !(endpos < buffer.length()); -} - - -bool load_ambdec_speakers(al::vector<AmbDecConf::SpeakerConf> &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer) -{ - while(spkrs.size() < num_speakers) - { - std::istringstream istr{buffer}; - - std::string cmd{read_word(istr)}; - if(cmd.empty()) - { - if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return false; - } - continue; - } - - if(cmd == "add_spkr") - { - spkrs.emplace_back(); - AmbDecConf::SpeakerConf &spkr = spkrs.back(); - const size_t spkr_num{spkrs.size()}; - - istr >> spkr.Name; - if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num); - istr >> spkr.Distance; - if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num); - istr >> spkr.Azimuth; - if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num); - istr >> spkr.Elevation; - if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num); - istr >> spkr.Connection; - if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num); - } - else - { - ERR("Unexpected speakers command: %s\n", cmd.c_str()); - return false; - } - - istr.clear(); - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return false; - } - buffer.clear(); - } - - return true; -} - -bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector<AmbDecConf::CoeffArray> &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer) -{ - bool gotgains{false}; - std::size_t cur{0u}; - while(cur < maxrow) - { - std::istringstream istr{buffer}; - - std::string cmd{read_word(istr)}; - if(cmd.empty()) - { - if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return false; - } - continue; - } - - if(cmd == "order_gain") - { - std::size_t curgain{0u}; - float value; - while(istr.good()) - { - istr >> value; - if(istr.fail()) break; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk on gain %zu: %s\n", curgain+1, - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return false; - } - if(curgain < size(gains)) - gains[curgain++] = value; - } - std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f); - gotgains = true; - } - else if(cmd == "add_row") - { - matrix.emplace_back(); - AmbDecConf::CoeffArray &mtxrow = matrix.back(); - std::size_t curidx{0u}; - float value{}; - while(istr.good()) - { - istr >> value; - if(istr.fail()) break; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk on matrix element %zux%zu: %s\n", curidx, - matrix.size(), buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - matrix.pop_back(); - return false; - } - if(curidx < mtxrow.size()) - mtxrow[curidx++] = value; - } - std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f); - cur++; - } - else - { - ERR("Unexpected matrix command: %s\n", cmd.c_str()); - return false; - } - - istr.clear(); - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return false; - } - buffer.clear(); - } - - if(!gotgains) - { - ERR("Matrix order_gain not specified\n"); - return false; - } - - return true; -} - -} // namespace - -int AmbDecConf::load(const char *fname) noexcept -{ - al::ifstream f{fname}; - if(!f.is_open()) - { - ERR("Failed to open: %s\n", fname); - return 0; - } - - std::size_t num_speakers{0u}; - std::string buffer; - while(read_clipped_line(f, buffer)) - { - std::istringstream istr{buffer}; - - std::string command{read_word(istr)}; - if(command.empty()) - { - ERR("Malformed line: %s\n", buffer.c_str()); - return 0; - } - - if(command == "/description") - istr >> Description; - else if(command == "/version") - { - istr >> Version; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after version: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - if(Version != 3) - { - ERR("Unsupported version: %u\n", Version); - return 0; - } - } - else if(command == "/dec/chan_mask") - { - istr >> std::hex >> ChanMask >> std::dec; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after mask: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - } - else if(command == "/dec/freq_bands") - { - istr >> FreqBands; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after freq_bands: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - if(FreqBands != 1 && FreqBands != 2) - { - ERR("Invalid freq_bands value: %u\n", FreqBands); - return 0; - } - } - else if(command == "/dec/speakers") - { - istr >> num_speakers; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after speakers: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - Speakers.reserve(num_speakers); - LFMatrix.reserve(num_speakers); - HFMatrix.reserve(num_speakers); - } - else if(command == "/dec/coeff_scale") - { - std::string scale = read_word(istr); - if(scale == "n3d") CoeffScale = AmbDecScale::N3D; - else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; - else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; - else - { - ERR("Unsupported coeff scale: %s\n", scale.c_str()); - return 0; - } - } - else if(command == "/opt/xover_freq") - { - istr >> XOverFreq; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after xover_freq: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - } - else if(command == "/opt/xover_ratio") - { - istr >> XOverRatio; - if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after xover_ratio: %s\n", - buffer.c_str()+static_cast<std::size_t>(istr.tellg())); - return 0; - } - } - else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" || - command == "/opt/delay_comp" || command == "/opt/level_comp") - { - /* Unused */ - read_word(istr); - } - else if(command == "/speakers/{") - { - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; - } - buffer.clear(); - - if(!load_ambdec_speakers(Speakers, num_speakers, f, buffer)) - return 0; - - if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return 0; - } - std::istringstream istr2{buffer}; - std::string endmark{read_word(istr2)}; - if(endmark != "/}") - { - ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str()); - return 0; - } - istr.swap(istr2); - } - else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") - { - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; - } - buffer.clear(); - - if(FreqBands == 1) - { - if(command != "/matrix/{") - { - ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str()); - return 0; - } - if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer)) - return 0; - } - else - { - if(command == "/lfmatrix/{") - { - if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer)) - return 0; - } - else if(command == "/hfmatrix/{") - { - if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer)) - return 0; - } - else - { - ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str()); - return 0; - } - } - - if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return 0; - } - std::istringstream istr2{buffer}; - std::string endmark{read_word(istr2)}; - if(endmark != "/}") - { - ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str()); - return 0; - } - istr.swap(istr2); - } - else if(command == "/end") - { - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos); - return 0; - } - - return 1; - } - else - { - ERR("Unexpected command: %s\n", command.c_str()); - return 0; - } - - istr.clear(); - const auto endpos = static_cast<std::size_t>(istr.tellg()); - if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; - } - buffer.clear(); - } - ERR("Unexpected end of file\n"); - - return 0; -} diff --git a/alc/ambdec.h b/alc/ambdec.h deleted file mode 100644 index ff7b71ee..00000000 --- a/alc/ambdec.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef AMBDEC_H -#define AMBDEC_H - -#include <array> -#include <string> - -#include "ambidefs.h" -#include "vector.h" - -/* Helpers to read .ambdec configuration files. */ - -enum class AmbDecScale { - N3D, - SN3D, - FuMa, -}; -struct AmbDecConf { - std::string Description; - int Version{0}; /* Must be 3 */ - - unsigned int ChanMask{0u}; - unsigned int FreqBands{0u}; /* Must be 1 or 2 */ - AmbDecScale CoeffScale{}; - - float XOverFreq{0.0f}; - float XOverRatio{0.0f}; - - struct SpeakerConf { - std::string Name; - float Distance{0.0f}; - float Azimuth{0.0f}; - float Elevation{0.0f}; - std::string Connection; - }; - al::vector<SpeakerConf> Speakers; - - using CoeffArray = std::array<float,MAX_AMBI_CHANNELS>; - /* Unused when FreqBands == 1 */ - float LFOrderGain[MAX_AMBI_ORDER+1]{}; - al::vector<CoeffArray> LFMatrix; - - float HFOrderGain[MAX_AMBI_ORDER+1]{}; - al::vector<CoeffArray> HFMatrix; - - int load(const char *fname) noexcept; -}; - -#endif /* AMBDEC_H */ diff --git a/alc/ambidefs.h b/alc/ambidefs.h deleted file mode 100644 index 9bc3e969..00000000 --- a/alc/ambidefs.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef AMBIDEFS_H -#define AMBIDEFS_H - -#include <array> -#include <cstdint> - -/* The maximum number of Ambisonics channels. For a given order (o), the size - * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- - * order has 9, third-order has 16, and fourth-order has 25. - */ -#define MAX_AMBI_ORDER 3 -constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept -{ return (order+1) * (order+1); } -#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER) - -/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up - * to 4th order, which is the highest order a 32-bit mask value can specify (a - * 64-bit mask could handle up to 7th order). - */ -#define AMBI_0ORDER_MASK 0x00000001 -#define AMBI_1ORDER_MASK 0x0000000f -#define AMBI_2ORDER_MASK 0x000001ff -#define AMBI_3ORDER_MASK 0x0000ffff -#define AMBI_4ORDER_MASK 0x01ffffff - -/* A bitmask of ambisonic channels with height information. If none of these - * channels are used/needed, there's no height (e.g. with most surround sound - * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc. - */ -#define AMBI_PERIPHONIC_MASK (0xfe7ce4) - -/* The maximum number of ambisonic channels for 2D (non-periphonic) - * representation. This is 2 per each order above zero-order, plus 1 for zero- - * order. Or simply, o*2 + 1. - */ -constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept -{ return order*2 + 1; } -#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER) - - -/* NOTE: These are scale factors as applied to Ambisonics content. Decoder - * coefficients should be divided by these values to get proper scalings. - */ -struct AmbiScale { - static constexpr std::array<float,MAX_AMBI_CHANNELS> FromN3D{{ - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f - }}; - static constexpr std::array<float,MAX_AMBI_CHANNELS> FromSN3D{{ - 1.000000000f, /* ACN 0, sqrt(1) */ - 1.732050808f, /* ACN 1, sqrt(3) */ - 1.732050808f, /* ACN 2, sqrt(3) */ - 1.732050808f, /* ACN 3, sqrt(3) */ - 2.236067978f, /* ACN 4, sqrt(5) */ - 2.236067978f, /* ACN 5, sqrt(5) */ - 2.236067978f, /* ACN 6, sqrt(5) */ - 2.236067978f, /* ACN 7, sqrt(5) */ - 2.236067978f, /* ACN 8, sqrt(5) */ - 2.645751311f, /* ACN 9, sqrt(7) */ - 2.645751311f, /* ACN 10, sqrt(7) */ - 2.645751311f, /* ACN 11, sqrt(7) */ - 2.645751311f, /* ACN 12, sqrt(7) */ - 2.645751311f, /* ACN 13, sqrt(7) */ - 2.645751311f, /* ACN 14, sqrt(7) */ - 2.645751311f, /* ACN 15, sqrt(7) */ - }}; - static constexpr std::array<float,MAX_AMBI_CHANNELS> FromFuMa{{ - 1.414213562f, /* ACN 0 (W), sqrt(2) */ - 1.732050808f, /* ACN 1 (Y), sqrt(3) */ - 1.732050808f, /* ACN 2 (Z), sqrt(3) */ - 1.732050808f, /* ACN 3 (X), sqrt(3) */ - 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ - 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ - 2.236067978f, /* ACN 6 (R), sqrt(5) */ - 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ - 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ - 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ - 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ - 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ - 2.645751311f, /* ACN 12 (K), sqrt(7) */ - 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ - 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ - 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ - }}; -}; - -struct AmbiIndex { - static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromFuMa{{ - 0, /* W */ - 3, /* X */ - 1, /* Y */ - 2, /* Z */ - 6, /* R */ - 7, /* S */ - 5, /* T */ - 8, /* U */ - 4, /* V */ - 12, /* K */ - 13, /* L */ - 11, /* M */ - 14, /* N */ - 10, /* O */ - 15, /* P */ - 9, /* Q */ - }}; - static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromACN{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; - - static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> From2D{{ - 0, 1,3, 4,8, 9,15 - }}; - static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> From3D{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; -}; - -#endif /* AMBIDEFS_H */ diff --git a/alc/backends/alsa.cpp b/alc/backends/alsa.cpp index 7dc3c3c4..d620a83c 100644 --- a/alc/backends/alsa.cpp +++ b/alc/backends/alsa.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/alsa.h" +#include "alsa.h" #include <algorithm> #include <atomic> @@ -35,18 +35,15 @@ #include <thread> #include <utility> -#include "AL/al.h" - #include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" -#include "alexcpt.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 "logging.h" #include "ringbuffer.h" #include "threads.h" #include "vector.h" @@ -56,7 +53,7 @@ namespace { -constexpr ALCchar alsaDevice[] = "ALSA Default"; +constexpr char alsaDevice[] = "ALSA Default"; #ifdef HAVE_DYNLOAD @@ -71,35 +68,37 @@ constexpr ALCchar alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_hw_params_free); \ MAGIC(snd_pcm_hw_params_any); \ MAGIC(snd_pcm_hw_params_current); \ + MAGIC(snd_pcm_hw_params_get_access); \ + MAGIC(snd_pcm_hw_params_get_buffer_size); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ + MAGIC(snd_pcm_hw_params_get_channels); \ + MAGIC(snd_pcm_hw_params_get_period_size); \ + MAGIC(snd_pcm_hw_params_get_period_time_max); \ + MAGIC(snd_pcm_hw_params_get_period_time_min); \ + MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_set_access); \ - MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ + MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ MAGIC(snd_pcm_hw_params_set_channels); \ + MAGIC(snd_pcm_hw_params_set_channels_near); \ + MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_period_time_near); \ + MAGIC(snd_pcm_hw_params_set_period_size_near); \ MAGIC(snd_pcm_hw_params_set_periods_near); \ MAGIC(snd_pcm_hw_params_set_rate_near); \ MAGIC(snd_pcm_hw_params_set_rate); \ MAGIC(snd_pcm_hw_params_set_rate_resample); \ - MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ - MAGIC(snd_pcm_hw_params_set_period_time_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ - MAGIC(snd_pcm_hw_params_set_period_size_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ - MAGIC(snd_pcm_hw_params_get_period_time_min); \ - MAGIC(snd_pcm_hw_params_get_period_time_max); \ - MAGIC(snd_pcm_hw_params_get_buffer_size); \ - MAGIC(snd_pcm_hw_params_get_period_size); \ - MAGIC(snd_pcm_hw_params_get_access); \ - MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_test_format); \ MAGIC(snd_pcm_hw_params_test_channels); \ MAGIC(snd_pcm_hw_params); \ - MAGIC(snd_pcm_sw_params_malloc); \ + MAGIC(snd_pcm_sw_params); \ MAGIC(snd_pcm_sw_params_current); \ + MAGIC(snd_pcm_sw_params_free); \ + MAGIC(snd_pcm_sw_params_malloc); \ MAGIC(snd_pcm_sw_params_set_avail_min); \ MAGIC(snd_pcm_sw_params_set_stop_threshold); \ - MAGIC(snd_pcm_sw_params); \ - MAGIC(snd_pcm_sw_params_free); \ MAGIC(snd_pcm_prepare); \ MAGIC(snd_pcm_start); \ MAGIC(snd_pcm_resume); \ @@ -108,7 +107,6 @@ constexpr ALCchar alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_delay); \ MAGIC(snd_pcm_state); \ MAGIC(snd_pcm_avail_update); \ - MAGIC(snd_pcm_areas_silence); \ MAGIC(snd_pcm_mmap_begin); \ MAGIC(snd_pcm_mmap_commit); \ MAGIC(snd_pcm_readi); \ @@ -134,7 +132,7 @@ constexpr ALCchar alsaDevice[] = "ALSA Default"; MAGIC(snd_card_next); \ MAGIC(snd_config_update_free_global) -static void *alsa_handle; +void *alsa_handle; #define MAKE_FUNC(f) decltype(f) * p##f ALSA_FUNCS(MAKE_FUNC); #undef MAKE_FUNC @@ -153,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 @@ -170,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 @@ -187,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 @@ -242,6 +241,11 @@ SwParamsPtr CreateSwParams() struct DevMap { std::string name; std::string device_name; + + template<typename T, typename U> + DevMap(T&& name_, U&& devname) + : name{std::forward<T>(name_)}, device_name{std::forward<U>(devname)} + { } }; al::vector<DevMap> PlaybackDevices; @@ -263,29 +267,36 @@ al::vector<DevMap> 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(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(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{ @@ -319,13 +330,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream) ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; int dev{-1}; - while(1) + while(true) { if(snd_ctl_pcm_next_device(handle, &dev) < 0) ERR("snd_ctl_pcm_next_device failed\n"); if(dev < 0) break; - snd_pcm_info_set_device(pcminfo, static_cast<ALuint>(dev)); + snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev)); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) @@ -361,7 +372,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream) device += ",DEV="; device += std::to_string(dev); - devlist.emplace_back(DevMap{std::move(name), std::move(device)}); + devlist.emplace_back(std::move(name), std::move(device)); const auto &entry = devlist.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } @@ -410,21 +421,24 @@ int verify_state(snd_pcm_t *handle) struct AlsaPlayback final : public BackendBase { - AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; int mixerProc(); int mixerNoMMapProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; + std::mutex mMutex; + + uint mFrameStep{}; al::vector<al::byte> mBuffer; std::atomic<bool> mKillNow{true}; @@ -454,7 +468,7 @@ int AlsaPlayback::mixerProc() if(state < 0) { ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); break; } @@ -492,7 +506,7 @@ int AlsaPlayback::mixerProc() avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop - std::lock_guard<AlsaPlayback> _{*this}; + std::lock_guard<std::mutex> _{mMutex}; while(avail > 0) { snd_pcm_uframes_t frames{avail}; @@ -507,10 +521,10 @@ int AlsaPlayback::mixerProc() } char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)}; - aluMixData(mDevice, WritePtr, static_cast<ALuint>(frames)); + mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep); snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; - if(commitres < 0 || (static_cast<snd_pcm_uframes_t>(commitres)-frames) != 0) + if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames) { ERR("mmap commit error: %s\n", snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres))); @@ -537,7 +551,7 @@ int AlsaPlayback::mixerNoMMapProc() if(state < 0) { ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); break; } @@ -571,10 +585,10 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - std::lock_guard<AlsaPlayback> _{*this}; al::byte *WritePtr{mBuffer.data()}; avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size())); - aluMixData(mDevice, WritePtr, static_cast<ALuint>(avail)); + std::lock_guard<std::mutex> _{mMutex}; + mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep); while(avail > 0) { snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, @@ -612,33 +626,37 @@ int AlsaPlayback::mixerNoMMapProc() } -void AlsaPlayback::open(const ALCchar *name) +void AlsaPlayback::open(const char *name) { - const char *driver{}; + std::string 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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; - driver = iter->device_name.c_str(); + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + driver = iter->device_name; } else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "device", "default"); + if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device")) + driver = std::move(driveropt).value(); } + TRACE("Opening device \"%s\"\n", driver.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.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "Could not open ALSA device \"%s\"", - driver}; + throw al::backend_exception{al::backend_error::NoDevice, + "Could not open ALSA device \"%s\"", driver.c_str()}; + 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(); @@ -674,16 +692,17 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)}; - ALuint periodLen{static_cast<ALuint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; - ALuint bufferLen{static_cast<ALuint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; - ALuint rate{mDevice->Frequency}; + bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)}; + uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; + uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; + uint rate{mDevice->Frequency}; int err{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if((err=(x)) < 0) \ - throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ @@ -720,37 +739,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.get<FrequencyRequest>()) + if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false) + || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) - 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) @@ -769,6 +776,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()}; @@ -779,58 +787,52 @@ bool AlsaPlayback::reset() #undef CHECK sp = nullptr; - mDevice->BufferSize = static_cast<ALuint>(bufferSizeInFrames); - mDevice->UpdateSize = static_cast<ALuint>(periodSizeInFrames); + mDevice->BufferSize = static_cast<uint>(bufferSizeInFrames); + mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames); mDevice->Frequency = rate; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); return true; } -bool AlsaPlayback::start() +void AlsaPlayback::start() { int err{}; snd_pcm_access_t access{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if((err=(x)) < 0) \ - throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); /* retrieve configuration info */ CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); -#undef CHECK hp = nullptr; int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { - mBuffer.resize( - static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize))); + auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); + mBuffer.resize(static_cast<size_t>(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } else { - err = snd_pcm_prepare(mPcmHandle); - if(err < 0) - { - ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err)); - return false; - } + CHECK(snd_pcm_prepare(mPcmHandle)); thread_func = &AlsaPlayback::mixerProc; } +#undef CHECK try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(thread_func), this}; - return true; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - mBuffer.clear(); - return false; } void AlsaPlayback::stop() @@ -840,13 +842,16 @@ void AlsaPlayback::stop() mThread.join(); mBuffer.clear(); + int err{snd_pcm_drop(mPcmHandle)}; + if(err < 0) + ERR("snd_pcm_drop failed: %s\n", snd_strerror(err)); } ClockLatency AlsaPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard<AlsaPlayback> _{*this}; + std::lock_guard<std::mutex> _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; @@ -863,14 +868,14 @@ 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 ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; @@ -893,33 +898,33 @@ AlsaCapture::~AlsaCapture() } -void AlsaCapture::open(const ALCchar *name) +void AlsaCapture::open(const char *name) { - const char *driver{}; + std::string 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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; - driver = iter->device_name.c_str(); + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + driver = iter->device_name; } else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "capture", "default"); + if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture")) + driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; + TRACE("Opening device \"%s\"\n", driver.c_str()); + int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; if(err < 0) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "Could not open ALSA device \"%s\"", - driver}; + throw al::backend_exception{al::backend_error::NoDevice, + "Could not open ALSA device \"%s\"", driver.c_str()}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); @@ -957,7 +962,8 @@ void AlsaCapture::open(const ALCchar *name) HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if((err=(x)) < 0) \ - throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ @@ -985,26 +991,25 @@ void AlsaCapture::open(const ALCchar *name) hp = nullptr; if(needring) - mRing = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); + mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); mDevice->DeviceName = name; } -bool AlsaCapture::start() +void AlsaCapture::start() { int err{snd_pcm_prepare(mPcmHandle)}; if(err < 0) - throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_prepare failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", snd_strerror(err)}; err = snd_pcm_start(mPcmHandle); if(err < 0) - throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_start failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", snd_strerror(err)}; mDoCapture = true; - return true; } void AlsaCapture::stop() @@ -1013,11 +1018,12 @@ void AlsaCapture::stop() * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's * available now so it'll be available later after the drop. */ - ALCuint avail{availableSamples()}; + uint avail{availableSamples()}; if(!mRing && avail > 0) { /* The ring buffer implicitly captures when checking availability. - * Direct access needs to explicitly capture it into temp storage. */ + * Direct access needs to explicitly capture it into temp storage. + */ auto temp = al::vector<al::byte>( static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); @@ -1029,12 +1035,12 @@ void AlsaCapture::stop() mDoCapture = false; } -ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples) +void AlsaCapture::captureSamples(al::byte *buffer, uint samples) { if(mRing) { mRing->read(buffer, samples); - return ALC_NO_ERROR; + return; } mLastAvail -= samples; @@ -1072,7 +1078,7 @@ ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples) { const char *err{snd_strerror(static_cast<int>(amt))}; ERR("restore error: %s\n", err); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); break; } /* If the amount available is less than what's asked, we lost it @@ -1083,16 +1089,14 @@ ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples) } buffer = buffer + amt; - samples -= static_cast<ALCuint>(amt); + samples -= static_cast<uint>(amt); } if(samples > 0) std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples), al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); - - return ALC_NO_ERROR; } -ALCuint AlsaCapture::availableSamples() +uint AlsaCapture::availableSamples() { snd_pcm_sframes_t avail{0}; if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture) @@ -1112,7 +1116,7 @@ ALCuint AlsaCapture::availableSamples() { const char *err{snd_strerror(static_cast<int>(avail))}; ERR("restore error: %s\n", err); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); } } @@ -1121,7 +1125,7 @@ ALCuint AlsaCapture::availableSamples() if(avail < 0) avail = 0; avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size())); if(avail > mLastAvail) mLastAvail = avail; - return static_cast<ALCuint>(mLastAvail); + return static_cast<uint>(mLastAvail); } while(avail > 0) @@ -1148,7 +1152,7 @@ ALCuint AlsaCapture::availableSamples() { const char *err{snd_strerror(static_cast<int>(amt))}; ERR("restore error: %s\n", err); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); break; } avail = amt; @@ -1159,14 +1163,13 @@ ALCuint AlsaCapture::availableSamples() avail -= amt; } - return static_cast<ALCuint>(mRing->readSpace()); + return static_cast<uint>(mRing->readSpace()); } ClockLatency AlsaCapture::getClockLatency() { ClockLatency ret; - std::lock_guard<AlsaCapture> _{*this}; ret.ClockTime = GetDeviceClockTime(mDevice); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; @@ -1197,10 +1200,10 @@ bool AlsaBackendFactory::init() if(!alsa_handle) { WARN("Failed to load %s\n", "libasound.so.2"); - return ALC_FALSE; + return false; } - error = ALC_FALSE; + error = false; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \ if(p##f == nullptr) { \ @@ -1226,30 +1229,34 @@ bool AlsaBackendFactory::init() bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void AlsaBackendFactory::probe(DevProbe type, std::string *outnames) +std::string AlsaBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; switch(type) { - case DevProbe::Playback: - PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; - case DevProbe::Capture: - CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Capture: + CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + + return outnames; } -BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new AlsaPlayback{device}}; diff --git a/alc/backends/alsa.h b/alc/backends/alsa.h index fb9de006..b256dcf5 100644 --- a/alc/backends/alsa.h +++ b/alc/backends/alsa.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_ALSA_H #define BACKENDS_ALSA_H -#include "backends/base.h" +#include "base.h" struct AlsaBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/base.cpp b/alc/backends/base.cpp index 25531cf5..e5ad8494 100644 --- a/alc/backends/base.cpp +++ b/alc/backends/base.cpp @@ -3,49 +3,54 @@ #include "base.h" +#include <algorithm> +#include <array> #include <atomic> -#include <thread> -#include "AL/al.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <mmreg.h> + +#include "albit.h" +#include "core/logging.h" +#include "aloptional.h" +#endif -#include "alcmain.h" -#include "alexcpt.h" -#include "alnumeric.h" #include "atomic.h" +#include "core/devformat.h" + +namespace al { -ClockLatency GetClockLatency(ALCdevice *device) +backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} { - BackendBase *backend{device->Backend.get()}; - ClockLatency ret{backend->getClockLatency()}; - ret.Latency += device->FixedLatency; - return ret; + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); } +backend_exception::~backend_exception() = default; +} // namespace al -/* BackendBase method implementations. */ -BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device} -{ } - -BackendBase::~BackendBase() = default; bool BackendBase::reset() -{ throw al::backend_exception{ALC_INVALID_DEVICE, "Invalid BackendBase call"}; } +{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -ALCenum BackendBase::captureSamples(al::byte*, ALCuint) -{ return ALC_INVALID_DEVICE; } +void BackendBase::captureSamples(al::byte*, uint) +{ } -ALCuint BackendBase::availableSamples() +uint BackendBase::availableSamples() { return 0; } ClockLatency BackendBase::getClockLatency() { ClockLatency ret; - ALuint refcount; + uint refcount; do { - while(((refcount=ReadRef(mDevice->MixCount))&1) != 0) - std::this_thread::yield(); + refcount = mDevice->waitForMix(); ret.ClockTime = GetDeviceClockTime(mDevice); std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != ReadRef(mDevice->MixCount)); @@ -60,3 +65,138 @@ ClockLatency BackendBase::getClockLatency() return ret; } + +void BackendBase::setDefaultWFXChannelOrder() +{ + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); + + switch(mDevice->FmtChans) + { + case DevFmtMono: + mDevice->RealOut.ChannelIndex[FrontCenter] = 0; + break; + case DevFmtStereo: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + break; + case DevFmtQuad: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + break; + case DevFmtX51: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[SideLeft] = 4; + mDevice->RealOut.ChannelIndex[SideRight] = 5; + break; + case DevFmtX61: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackCenter] = 4; + mDevice->RealOut.ChannelIndex[SideLeft] = 5; + mDevice->RealOut.ChannelIndex[SideRight] = 6; + break; + case DevFmtX71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + break; + case DevFmtX714: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + break; + case DevFmtX3D71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[Aux0] = 4; + mDevice->RealOut.ChannelIndex[Aux1] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + break; + case DevFmtAmbi3D: + break; + } +} + +void BackendBase::setDefaultChannelOrder() +{ + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); + + switch(mDevice->FmtChans) + { + case DevFmtX51: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[SideLeft] = 2; + mDevice->RealOut.ChannelIndex[SideRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + return; + case DevFmtX71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + return; + case DevFmtX714: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + break; + case DevFmtX3D71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[Aux0] = 2; + mDevice->RealOut.ChannelIndex[Aux1] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + return; + + /* Same as WFX order */ + case DevFmtMono: + case DevFmtStereo: + case DevFmtQuad: + case DevFmtX61: + case DevFmtAmbi3D: + setDefaultWFXChannelOrder(); + break; + } +} diff --git a/alc/backends/base.h b/alc/backends/base.h index d4856818..b6b3d922 100644 --- a/alc/backends/base.h +++ b/alc/backends/base.h @@ -2,70 +2,75 @@ #define ALC_BACKENDS_BASE_H #include <chrono> +#include <cstdarg> #include <memory> -#include <mutex> +#include <ratio> #include <string> -#include "AL/alc.h" - -#include "alcmain.h" #include "albyte.h" +#include "core/device.h" +#include "core/except.h" + +using uint = unsigned int; struct ClockLatency { std::chrono::nanoseconds ClockTime; std::chrono::nanoseconds Latency; }; -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - -ClockLatency GetClockLatency(ALCdevice *device); - struct BackendBase { - virtual void open(const ALCchar *name) = 0; + virtual void open(const char *name) = 0; virtual bool reset(); - virtual bool start() = 0; + virtual void start() = 0; virtual void stop() = 0; - virtual ALCenum captureSamples(al::byte *buffer, ALCuint samples); - virtual ALCuint availableSamples(); + virtual void captureSamples(al::byte *buffer, uint samples); + virtual uint availableSamples(); virtual ClockLatency getClockLatency(); - virtual void lock() { mMutex.lock(); } - virtual void unlock() { mMutex.unlock(); } + DeviceBase *const mDevice; - ALCdevice *mDevice; + BackendBase(DeviceBase *device) noexcept : mDevice{device} { } + virtual ~BackendBase() = default; - std::recursive_mutex mMutex; - - BackendBase(ALCdevice *device) noexcept; - virtual ~BackendBase(); +protected: + /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ + void setDefaultChannelOrder(); + /** Sets the default channel order used by WaveFormatEx. */ + void setDefaultWFXChannelOrder(); }; using BackendPtr = std::unique_ptr<BackendBase>; -using BackendUniqueLock = std::unique_lock<BackendBase>; -using BackendLockGuard = std::lock_guard<BackendBase>; enum class BackendType { Playback, Capture }; -enum class DevProbe { - Playback, - Capture -}; + +/* Helper to get the current clock time from the device's ClockBase, and + * SamplesDone converted from the sample rate. + */ +inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) +{ + using std::chrono::seconds; + using std::chrono::nanoseconds; + + auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; + return device->ClockBase + ns; +} + +/* Helper to get the device latency from the backend, including any fixed + * latency from post-processing. + */ +inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) +{ + ClockLatency ret{backend->getClockLatency()}; + ret.Latency += device->FixedLatency; + return ret; +} struct BackendFactory { @@ -73,12 +78,37 @@ struct BackendFactory { virtual bool querySupport(BackendType type) = 0; - virtual void probe(DevProbe type, std::string *outnames) = 0; + virtual std::string probe(BackendType type) = 0; - virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; + virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; protected: virtual ~BackendFactory() = default; }; +namespace al { + +enum class backend_error { + NoDevice, + DeviceError, + OutOfMemory +}; + +class backend_exception final : public base_exception { + backend_error mErrorCode; + +public: +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + backend_exception(backend_error code, const char *msg, ...); + ~backend_exception() override; + + backend_error errorCode() const noexcept { return mErrorCode; } +}; + +} // namespace al + #endif /* ALC_BACKENDS_BASE_H */ diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp index 7c18287b..8b0e75fd 100644 --- a/alc/backends/coreaudio.cpp +++ b/alc/backends/coreaudio.cpp @@ -20,31 +20,241 @@ #include "config.h" -#include "backends/coreaudio.h" +#include "coreaudio.h" +#include <inttypes.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> + +#include <cmath> +#include <memory> +#include <string> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/logging.h" #include "ringbuffer.h" -#include "converter.h" -#include "backends/base.h" -#include <unistd.h> #include <AudioUnit/AudioUnit.h> #include <AudioToolbox/AudioToolbox.h> namespace { -static const ALCchar ca_device[] = "CoreAudio Default"; +#if TARGET_OS_IOS || TARGET_OS_TV +#define CAN_ENUMERATE 0 +#else +#define CAN_ENUMERATE 1 +#endif + +constexpr auto OutputElement = 0; +constexpr auto InputElement = 1; + +#if CAN_ENUMERATE +struct DeviceEntry { + AudioDeviceID mId; + std::string mName; +}; + +std::vector<DeviceEntry> PlaybackList; +std::vector<DeviceEntry> 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<size_t>(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<char[]>(propSize); + auto *buflist = reinterpret_cast<AudioBufferList*>(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<DeviceEntry> &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<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); + if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) + { + ERR("Failed to get device list: %u\n", err); + return; + } + + std::vector<DeviceEntry> 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, @@ -58,14 +268,14 @@ struct CoreAudioPlayback final : public BackendBase { inBusNumber, inNumberFrames, ioData); } - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; AudioUnit mAudioUnit{}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD DEF_NEWDEL(CoreAudioPlayback) @@ -81,26 +291,53 @@ CoreAudioPlayback::~CoreAudioPlayback() OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, AudioBufferList *ioData) noexcept { - std::lock_guard<CoreAudioPlayback> _{*this}; - aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize); + for(size_t i{0};i < ioData->mNumberBuffers;++i) + { + auto &buffer = ioData->mBuffers[i]; + mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize, + buffer.mNumberChannels); + } return noErr; } -void CoreAudioPlayback::open(const ALCchar *name) +void CoreAudioPlayback::open(const char *name) { +#if CAN_ENUMERATE + AudioDeviceID audioDevice{kAudioDeviceUnknown}; + if(!name) + GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), + &audioDevice); + else + { + if(PlaybackList.empty()) + EnumerateDevices(PlaybackList, false); + + auto find_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); + if(devmatch == PlaybackList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + audioDevice = devmatch->mId; + } +#else if(!name) name = ca_device; else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; +#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; @@ -108,19 +345,52 @@ void CoreAudioPlayback::open(const ALCchar *name) AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == nullptr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"}; + 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{ALC_INVALID_VALUE, "Could not create component instance: %u", - err}; + 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, OutputElement, &audioDevice, sizeof(AudioDeviceID)); +#endif + + err = AudioUnitInitialize(audioUnit); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err}; + 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, OutputElement, &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() @@ -131,10 +401,10 @@ bool CoreAudioPlayback::reset() /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; - auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription)); + UInt32 size{sizeof(streamFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 0, &streamFormat, &size); - if(err != noErr || size != sizeof(AudioStreamBasicDescription)) + OutputElement, &streamFormat, &size); + if(err != noErr || size != sizeof(streamFormat)) { ERR("AudioUnitGetProperty failed\n"); return false; @@ -150,99 +420,65 @@ 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<ALuint>(uint64_t{mDevice->BufferSize} * - streamFormat.mSampleRate / mDevice->Frequency); - mDevice->Frequency = static_cast<ALuint>(streamFormat.mSampleRate); + mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/ + mDevice->Frequency + 0.5); + mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate); } /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? eg, 6.0 vs 5.1 */ - switch(streamFormat.mChannelsPerFrame) - { - case 1: - mDevice->FmtChans = DevFmtMono; - break; - case 2: - mDevice->FmtChans = DevFmtStereo; - break; - case 4: - mDevice->FmtChans = DevFmtQuad; - break; - case 6: - mDevice->FmtChans = DevFmtX51; - break; - case 7: - mDevice->FmtChans = DevFmtX61; - break; - case 8: - mDevice->FmtChans = DevFmtX71; - break; - default: - ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame); - mDevice->FmtChans = DevFmtStereo; - streamFormat.mChannelsPerFrame = 2; - break; - } - SetDefaultWFXChannelOrder(mDevice); + * to specify what we're giving? e.g. 6.0 vs 5.1 + */ + streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); - /* use channel count and sample rate from the default output unit's current - * parameters, but reset everything else */ streamFormat.mFramesPerPacket = 1; - streamFormat.mFormatFlags = 0; + streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; + streamFormat.mFormatID = kAudioFormatLinearPCM; switch(mDevice->FmtType) { case DevFmtUByte: mDevice->FmtType = DevFmtByte; /* fall-through */ case DevFmtByte: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 16; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 32; break; case DevFmtFloat: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat; streamFormat.mBitsPerChannel = 32; break; } - streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * - streamFormat.mBitsPerChannel / 8; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian | - kLinearPCMFormatFlagIsPacked; + streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(AudioStreamBasicDescription)); + OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { ERR("AudioUnitSetProperty failed\n"); return false; } + setDefaultWFXChannelOrder(); + /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; @@ -250,7 +486,7 @@ bool CoreAudioPlayback::reset() input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct)); + kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { ERR("AudioUnitSetProperty failed\n"); @@ -268,15 +504,12 @@ bool CoreAudioPlayback::reset() return true; } -bool CoreAudioPlayback::start() +void CoreAudioPlayback::start() { - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; + const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return false; - } - return true; + throw al::backend_exception{al::backend_error::DeviceError, + "AudioOutputUnitStart failed: %d", err}; } void CoreAudioPlayback::stop() @@ -288,7 +521,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, @@ -302,19 +535,21 @@ struct CoreAudioCapture final : public BackendBase { inBusNumber, inNumberFrames, ioData); } - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; AudioUnit mAudioUnit{0}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD SampleConverterPtr mConverter; + al::vector<char> mCaptureData; + RingBufferPtr mRing{nullptr}; DEF_NEWDEL(CoreAudioCapture) @@ -328,181 +563,176 @@ CoreAudioCapture::~CoreAudioCapture() } -OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames, +OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList*) noexcept { - AudioUnitRenderActionFlags flags = 0; union { - al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; + al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; - auto rec_vec = mRing->getWriteVector(); - inNumberFrames = static_cast<UInt32>(minz(inNumberFrames, - rec_vec.first.len+rec_vec.second.len)); + audiobuf.list.mNumberBuffers = 1; + audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; + audiobuf.list.mBuffers[0].mData = mCaptureData.data(); + audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size()); - // Fill the ringbuffer's two segments with data from the input device - if(rec_vec.first.len >= inNumberFrames) - { - audiobuf.list.mNumberBuffers = 1; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame; - } - else - { - const auto remaining = static_cast<ALuint>(inNumberFrames - rec_vec.first.len); - audiobuf.list.mNumberBuffers = 2; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) * - mFormat.mBytesPerFrame; - audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[1].mData = rec_vec.second.buf; - audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame; - } - OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers, + OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender error: %d\n", err); + ERR("AudioUnitRender capture error: %d\n", err); return err; } - mRing->writeAdvance(inNumberFrames); + mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } -void CoreAudioCapture::open(const ALCchar *name) +void CoreAudioCapture::open(const char *name) { - AudioStreamBasicDescription requestedFormat; // The application requested format - AudioStreamBasicDescription hardwareFormat; // The hardware format - AudioStreamBasicDescription outputFormat; // The AudioUnit output format - AURenderCallbackStruct input; - AudioComponentDescription desc; - UInt32 outputFrameCount; - UInt32 propertySize; -#if !TARGET_OS_IOS - AudioObjectPropertyAddress propertyAddress; -#endif - 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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + 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{ALC_INVALID_VALUE, "Could not find audio component"}; + 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{ALC_INVALID_VALUE, "Could not create component instance: %u", - err}; + throw al::backend_exception{al::backend_error::NoDevice, + "Could not create component instance: %u", err}; // Turn off AudioUnit output - enableIO = 0; + UInt32 enableIO{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint)); + kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, "Could not disable audio unit output property: %u", err}; // Turn on AudioUnit input enableIO = 1; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint)); + kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, + 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); - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, - &propertySize, &inputDevice); - if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err}; - if(inputDevice == kAudioDeviceUnknown) - throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not set input device: %u", err}; - } +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID)); #endif // set capture callback + AURenderCallbackStruct input{}; input.inputProc = CoreAudioCapture::RecordProcC; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); + kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); + if(err != noErr) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not set capture callback: %u", err}; + + // Disable buffer allocation for capture + UInt32 flag{0}; + err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer, + kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not set capture callback: %u", err}; + throw al::backend_exception{al::backend_error::DeviceError, + "Could not disable buffer allocation property: %u", err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err}; + throw al::backend_exception{al::backend_error::DeviceError, + "Could not initialize audio unit: %u", err}; // Get the hardware format - propertySize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription hardwareFormat{}; + UInt32 propertySize{sizeof(hardwareFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 1, &hardwareFormat, &propertySize); - if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input format: %u", err}; + InputElement, &hardwareFormat, &propertySize); + 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: + requestedFormat.mBitsPerChannel = 8; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + break; case DevFmtUByte: requestedFormat.mBitsPerChannel = 8; requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; break; case DevFmtShort: requestedFormat.mBitsPerChannel = 16; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtUShort: + requestedFormat.mBitsPerChannel = 16; + requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtInt: requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtUInt: + requestedFormat.mBitsPerChannel = 32; + requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtFloat: requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted", - DevFmtTypeString(mDevice->FmtType)}; } switch(mDevice->FmtChans) @@ -516,11 +746,12 @@ void CoreAudioCapture::open(const ALCchar *name) case DevFmtQuad: case DevFmtX51: - case DevFmtX51Rear: case DevFmtX61: case DevFmtX71: + case DevFmtX714: + case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -537,53 +768,73 @@ void CoreAudioCapture::open(const ALCchar *name) // Use intermediate format for sample rate conversion (outputFormat) // Set sample rate to the same as hardware for resampling later - outputFormat = requestedFormat; + AudioStreamBasicDescription outputFormat{requestedFormat}; outputFormat.mSampleRate = hardwareFormat.mSampleRate; // The output format should be the requested format, but using the hardware sample rate // This is because the AudioUnit will automatically scale other properties, except for sample rate err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 1, &outputFormat, sizeof(outputFormat)); + InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input format: %u", err}; - - // Set the AudioUnit output format frame count - uint64_t FrameCount64{mDevice->UpdateSize}; - FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) / - mDevice->Frequency; - FrameCount64 += MAX_RESAMPLER_PADDING; - if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2) - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, + "Could not set input format: %u", err}; + + /* Calculate the minimum AudioUnit output format frame count for the pre- + * conversion ring buffer. Ensure at least 100ms for the total buffer. + */ + double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; + auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)), + static_cast<UInt32>(outputFormat.mSampleRate)/10); + FrameCount64 += MaxResamplerPadding; + if(FrameCount64 > std::numeric_limits<int32_t>::max()) + throw al::backend_exception{al::backend_error::DeviceError, "Calculated frame count is too large: %" PRIu64, FrameCount64}; - outputFrameCount = static_cast<uint32_t>(FrameCount64); - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount)); - if(err != noErr) - throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u", - err}; + UInt32 outputFrameCount{}; + propertySize = sizeof(outputFrameCount); + err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); + if(err != noErr || propertySize != sizeof(outputFrameCount)) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not get input frame count: %u", err}; + + mCaptureData.resize(outputFrameCount * mFrameSize); + + outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64)); + mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); - // Set up sample converter if needed + /* Set up sample converter if needed */ if(outputFormat.mSampleRate != mDevice->Frequency) - mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, - mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate), + mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, + mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate), mDevice->Frequency, Resampler::FastBSinc24); - mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false); - +#if CAN_ENUMERATE + if(name) + mDevice->DeviceName = name; + else + { + UInt32 propSize{sizeof(audioDevice)}; + audioDevice = kAudioDeviceUnknown; + AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, InputElement, &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 CoreAudioCapture::start() +void CoreAudioCapture::start() { OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return false; - } - return true; + throw al::backend_exception{al::backend_error::DeviceError, + "AudioOutputUnitStart failed: %d", err}; } void CoreAudioCapture::stop() @@ -593,35 +844,34 @@ void CoreAudioCapture::stop() ERR("AudioOutputUnitStop failed\n"); } -ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples) +void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) { if(!mConverter) { mRing->read(buffer, samples); - return ALC_NO_ERROR; + return; } auto rec_vec = mRing->getReadVector(); const void *src0{rec_vec.first.buf}; - auto src0len = static_cast<ALuint>(rec_vec.first.len); - ALuint got{mConverter->convert(&src0, &src0len, buffer, samples)}; + auto src0len = static_cast<uint>(rec_vec.first.len); + uint got{mConverter->convert(&src0, &src0len, buffer, samples)}; size_t total_read{rec_vec.first.len - src0len}; if(got < samples && !src0len && rec_vec.second.len > 0) { const void *src1{rec_vec.second.buf}; - auto src1len = static_cast<ALuint>(rec_vec.second.len); - got += mConverter->convert(&src1, &src1len, buffer+got, samples-got); + auto src1len = static_cast<uint>(rec_vec.second.len); + got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); total_read += rec_vec.second.len - src1len; } mRing->readAdvance(total_read); - return ALC_NO_ERROR; } -ALCuint CoreAudioCapture::availableSamples() +uint CoreAudioCapture::availableSamples() { - if(!mConverter) return static_cast<ALCuint>(mRing->readSpace()); - return mConverter->availableOut(static_cast<ALCuint>(mRing->readSpace())); + if(!mConverter) return static_cast<uint>(mRing->readSpace()); + return mConverter->availableOut(static_cast<uint>(mRing->readSpace())); } } // namespace @@ -637,19 +887,42 @@ bool CoreAudioBackendFactory::init() { return true; } bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames) +std::string CoreAudioBackendFactory::probe(BackendType type) { + std::string outnames; +#if CAN_ENUMERATE + auto append_name = [&outnames](const DeviceEntry &entry) -> void + { + /* Includes null char. */ + outnames.append(entry.mName.c_str(), entry.mName.length()+1); + }; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(ca_device, sizeof(ca_device)); - break; + case BackendType::Playback: + EnumerateDevices(PlaybackList, false); + std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); + break; + case BackendType::Capture: + EnumerateDevices(CaptureList, true); + std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); + break; } + +#else + + switch(type) + { + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(ca_device, sizeof(ca_device)); + break; + } +#endif + return outnames; } -BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new CoreAudioPlayback{device}}; diff --git a/alc/backends/coreaudio.h b/alc/backends/coreaudio.h index 37b9ebe5..1252edde 100644 --- a/alc/backends/coreaudio.h +++ b/alc/backends/coreaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H -#include "backends/base.h" +#include "base.h" struct CoreAudioBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/dsound.cpp b/alc/backends/dsound.cpp index c04ba9e4..f549c0fe 100644 --- a/alc/backends/dsound.cpp +++ b/alc/backends/dsound.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/dsound.h" +#include "dsound.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> @@ -44,12 +44,13 @@ #include <algorithm> #include <functional> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" -#include "ringbuffer.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" #include "strutils.h" #include "threads.h" @@ -107,6 +108,15 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac #endif +#define MONO SPEAKER_FRONT_CENTER +#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) +#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) + #define MAX_UPDATES 128 struct DevMap { @@ -161,21 +171,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; - bool start() override; + void start() override; void stop() override; - IDirectSound *mDS{nullptr}; - IDirectSoundBuffer *mPrimaryBuffer{nullptr}; - IDirectSoundBuffer *mBuffer{nullptr}; - IDirectSoundNotify *mNotifies{nullptr}; - HANDLE mNotifyEvent{nullptr}; + ComPtr<IDirectSound> mDS; + ComPtr<IDirectSoundBuffer> mPrimaryBuffer; + ComPtr<IDirectSoundBuffer> mBuffer; + ComPtr<IDirectSoundNotify> mNotifies; + HANDLE mNotifyEvent{nullptr}; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -185,19 +195,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; @@ -215,18 +217,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(FAILED(err)) { ERR("Failed to get buffer caps: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err); + mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err); return 1; } - ALuint FrameSize{mDevice->frameSizeFromFmt()}; + const size_t FrameStep{mDevice->channelsFromFmt()}; + uint FrameSize{mDevice->frameSizeFromFmt()}; DWORD FragSize{mDevice->UpdateSize * FrameSize}; bool Playing{false}; DWORD LastCursor{0u}; mBuffer->GetCurrentPosition(&LastCursor, nullptr); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { // Get current play cursor DWORD PlayCursor; @@ -241,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(FAILED(err)) { ERR("Failed to play buffer: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err); + mDevice->handleDisconnect("Failure starting playback: 0x%lx", err); return 1; } Playing = true; @@ -275,19 +278,16 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(SUCCEEDED(err)) { - std::unique_lock<DSoundPlayback> dlock{*this}; - aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize); + mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); if(WriteCnt2 > 0) - aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize); - dlock.unlock(); + mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); } else { ERR("Buffer lock error: %#lx\n", err); - std::lock_guard<DSoundPlayback> _{*this}; - aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err); + mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err); return 1; } @@ -299,7 +299,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -void DSoundPlayback::open(const ALCchar *name) +void DSoundPlayback::open(const char *name) { HRESULT hr; if(PlaybackDevices.empty()) @@ -322,155 +322,124 @@ void DSoundPlayback::open(const ALCchar *name) else { auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + { + GUID id{}; + hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); + if(SUCCEEDED(hr)) + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&id](const DevMap &entry) -> bool { return entry.guid == id; }); + if(iter == PlaybackDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + } guid = &iter->guid; } hr = DS_OK; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(!mNotifyEvent) hr = E_FAIL; + if(!mNotifyEvent) + { + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(!mNotifyEvent) hr = E_FAIL; + } //DirectSound Init code + ComPtr<IDirectSound> 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{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", 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) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtFloat: - if(mDevice->Flags.get<SampleTypeRequest>()) - break; - /* fall-through */ - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + break; + case DevFmtFloat: + if(mDevice->Flags.test(SampleTypeRequest)) break; + /* fall-through */ + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + break; } WAVEFORMATEXTENSIBLE OutputType{}; - DWORD speakers; + DWORD speakers{}; HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; - if(SUCCEEDED(hr)) + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get speaker config: 0x%08lx", hr}; + + speakers = DSSPEAKER_CONFIG(speakers); + if(!mDevice->Flags.test(ChannelsRequest)) { - speakers = DSSPEAKER_CONFIG(speakers); - if(!mDevice->Flags.get<ChannelsRequest>()) - { - if(speakers == DSSPEAKER_MONO) - mDevice->FmtChans = DevFmtMono; - else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) - mDevice->FmtChans = DevFmtStereo; - else if(speakers == DSSPEAKER_QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(speakers == DSSPEAKER_5POINT1_SURROUND) - 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); + if(speakers == DSSPEAKER_MONO) + mDevice->FmtChans = DevFmtMono; + else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) + mDevice->FmtChans = DevFmtStereo; + else if(speakers == DSSPEAKER_QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) + mDevice->FmtChans = DevFmtX51; + else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) + mDevice->FmtChans = DevFmtX71; + else + ERR("Unknown system speaker config: 0x%lx\n", speakers); + } + mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); + const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - } + switch(mDevice->FmtChans) + { + case DevFmtMono: OutputType.dwChannelMask = MONO; break; + case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; + /* fall-through */ + case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; + case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; + case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; + case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; + case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; + } retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt()); - OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8); - OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = 0; - } + hr = S_OK; + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt()); + OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8); + OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nSamplesPerSec = mDevice->Frequency; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + OutputType.Format.cbSize = 0; if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { @@ -482,8 +451,6 @@ retry_open: else OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; } else @@ -493,7 +460,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); @@ -501,19 +468,19 @@ retry_open: if(SUCCEEDED(hr)) { - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; mDevice->BufferSize = mDevice->UpdateSize * num_updates; DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | - DSBCAPS_GLOBALFOCUS; + DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 + | DSBCAPS_GLOBALFOCUS; DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) { mDevice->FmtType = DevFmtShort; @@ -527,56 +494,46 @@ retry_open: hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); if(SUCCEEDED(hr)) { - auto Notifies = static_cast<IDirectSoundNotify*>(ptr); - mNotifies = Notifies; + mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)}; - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots; - for(ALuint i{0};i < num_updates;++i) + for(uint i{0};i < num_updates;++i) { nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; nots[i].hEventNotify = mNotifyEvent; } - if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) + if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) hr = E_FAIL; } } if(FAILED(hr)) { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; return false; } ResetEvent(mNotifyEvent); - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); return true; } -bool DSoundPlayback::start() +void DSoundPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - return false; } void DSoundPlayback::stop() @@ -590,17 +547,17 @@ 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 ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - IDirectSoundCapture *mDSC{nullptr}; - IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; + ComPtr<IDirectSoundCapture> mDSC; + ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer; DWORD mBufferBytes{0u}; DWORD mCursor{0u}; @@ -614,17 +571,13 @@ DSoundCapture::~DSoundCapture() if(mDSCbuffer) { mDSCbuffer->Stop(); - mDSCbuffer->Release(); mDSCbuffer = nullptr; } - - if(mDSC) - mDSC->Release(); mDSC = nullptr; } -void DSoundCapture::open(const ALCchar *name) +void DSoundCapture::open(const char *name) { HRESULT hr; if(CaptureDevices.empty()) @@ -647,11 +600,18 @@ void DSoundCapture::open(const ALCchar *name) else { auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + { + GUID id{}; + hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); + if(SUCCEEDED(hr)) + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&id](const DevMap &entry) -> bool { return entry.guid == id; }); + if(iter == CaptureDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + } guid = &iter->guid; } @@ -661,8 +621,8 @@ void DSoundCapture::open(const ALCchar *name) case DevFmtUShort: case DevFmtUInt: WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; case DevFmtUByte: case DevFmtShort: @@ -674,36 +634,17 @@ void DSoundCapture::open(const ALCchar *name) WAVEFORMATEXTENSIBLE InputType{}; switch(mDevice->FmtChans) { - case DevFmtMono: - InputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtStereo: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; + case DevFmtMono: InputType.dwChannelMask = MONO; break; + case DevFmtStereo: InputType.dwChannelMask = STEREO; break; + case DevFmtQuad: InputType.dwChannelMask = QUAD; break; + case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; + case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; + case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX3D71: case DevFmtAmbi3D: WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -728,7 +669,7 @@ void DSoundCapture::open(const ALCchar *name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - ALuint samples{mDevice->BufferSize}; + uint samples{mDevice->BufferSize}; samples = maxu(samples, 100 * mDevice->Frequency / 1000); DSCBUFFERDESC DSCBDescription{}; @@ -738,41 +679,34 @@ void DSoundCapture::open(const ALCchar *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); + hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); if(SUCCEEDED(hr)) - mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false); + 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{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr}; + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + hr}; } mBufferBytes = DSCBDescription.dwBufferBytes; - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); mDevice->DeviceName = name; } -bool DSoundCapture::start() +void DSoundCapture::start() { - HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; + const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; if(FAILED(hr)) - { - ERR("start failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr); - return false; - } - return true; + throw al::backend_exception{al::backend_error::DeviceError, + "Failure starting capture: 0x%lx", hr}; } void DSoundCapture::stop() @@ -781,24 +715,21 @@ void DSoundCapture::stop() if(FAILED(hr)) { ERR("stop failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr); + mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr); } } -ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void DSoundCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint DSoundCapture::availableSamples() +uint DSoundCapture::availableSamples() { if(!mDevice->Connected.load(std::memory_order_acquire)) - return static_cast<ALCuint>(mRing->readSpace()); + return static_cast<uint>(mRing->readSpace()); - ALuint FrameSize{mDevice->frameSizeFromFmt()}; - DWORD BufferBytes{mBufferBytes}; - DWORD LastCursor{mCursor}; + const uint FrameSize{mDevice->frameSizeFromFmt()}; + const DWORD BufferBytes{mBufferBytes}; + const DWORD LastCursor{mCursor}; DWORD ReadCursor{}; void *ReadPtr1{}, *ReadPtr2{}; @@ -806,8 +737,8 @@ ALCuint DSoundCapture::availableSamples() HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)}; if(SUCCEEDED(hr)) { - DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes}; - if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace()); + const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes}; + if(!NumBytes) return static_cast<uint>(mRing->readSpace()); hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); } if(SUCCEEDED(hr)) @@ -816,16 +747,16 @@ ALCuint DSoundCapture::availableSamples() if(ReadPtr2 != nullptr && ReadCnt2 > 0) mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); - mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes; + mCursor = ReadCursor; } if(FAILED(hr)) { ERR("update failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr); + mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr); } - return static_cast<ALCuint>(mRing->readSpace()); + return static_cast<uint>(mRing->readSpace()); } } // namespace @@ -871,14 +802,15 @@ bool DSoundBackendFactory::init() bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) +std::string DSoundBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; /* Initialize COM to prevent name truncation */ @@ -886,27 +818,29 @@ void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) HRESULT hrcom{CoInitialize(nullptr)}; switch(type) { - case DevProbe::Playback: - PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices.clear(); + hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); + if(FAILED(hr)) + ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; - case DevProbe::Capture: - CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Capture: + CaptureDevices.clear(); + hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); + if(FAILED(hr)) + ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } if(SUCCEEDED(hrcom)) CoUninitialize(); + + return outnames; } -BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new DSoundPlayback{device}}; diff --git a/alc/backends/dsound.h b/alc/backends/dsound.h index 6bef0bfc..787f227a 100644 --- a/alc/backends/dsound.h +++ b/alc/backends/dsound.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H -#include "backends/base.h" +#include "base.h" struct DSoundBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp index c7bf8469..791002ca 100644 --- a/alc/backends/jack.cpp +++ b/alc/backends/jack.cpp @@ -20,19 +20,22 @@ #include "config.h" -#include "backends/jack.h" +#include "jack.h" #include <cstdlib> #include <cstdio> +#include <cstring> #include <memory.h> +#include <array> #include <thread> #include <functional> -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "alexcpt.h" +#include "alc/alconfig.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" #include "threads.h" @@ -43,9 +46,6 @@ namespace { -constexpr ALCchar jackDevice[] = "JACK Default"; - - #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -70,7 +70,7 @@ constexpr ALCchar jackDevice[] = "JACK Default"; void *jack_handle; #define MAKE_FUNC(f) decltype(f) * p##f -JACK_FUNCS(MAKE_FUNC); +JACK_FUNCS(MAKE_FUNC) decltype(jack_error_callback) * pjack_error_callback; #undef MAKE_FUNC @@ -99,11 +99,13 @@ decltype(jack_error_callback) * pjack_error_callback; #endif +constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; + jack_options_t ClientOptions = JackNullOption; -ALCboolean jack_load() +bool jack_load() { - ALCboolean error = ALC_FALSE; + bool error{false}; #ifdef HAVE_DYNLOAD if(!jack_handle) @@ -119,14 +121,14 @@ ALCboolean jack_load() if(!jack_handle) { WARN("Failed to load %s\n", JACKLIB); - return ALC_FALSE; + return false; } - error = ALC_FALSE; + error = false; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \ if(p##f == nullptr) { \ - error = ALC_TRUE; \ + error = true; \ missing_funcs += "\n" #f; \ } \ } while(0) @@ -150,13 +152,142 @@ ALCboolean jack_load() } +struct JackDeleter { + void operator()(void *ptr) { jack_free(ptr); } +}; +using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; + +struct DeviceEntry { + std::string mName; + std::string mPattern; + + template<typename T, typename U> + DeviceEntry(T&& name, U&& pattern) + : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)} + { } +}; + +al::vector<DeviceEntry> PlaybackList; + + +void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list) +{ + std::remove_reference_t<decltype(list)>{}.swap(list); + + if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)}) + { + for(size_t i{0};ports[i];++i) + { + const char *sep{std::strchr(ports[i], ':')}; + if(!sep || ports[i] == sep) continue; + + const al::span<const char> 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(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("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<const char> name{listopt->data()+strpos, seppos-strpos}; + const al::span<const char> 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(std::string{name.data(), name.size()}, + std::string{pattern.data(), pattern.size()}); + const auto &entry = list.back(); + TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + } + + if(nextpos != std::string::npos) ++nextpos; + strpos = nextpos; + } + } + + if(list.size() > 1) + { + /* Rename entries that have matching names, by appending '#2', '#3', + * etc, as needed. + */ + for(auto curitem = list.begin()+1;curitem != list.end();++curitem) + { + auto check_match = [curitem](const DeviceEntry &entry) -> bool + { return entry.mName == curitem->mName; }; + if(std::find_if(list.begin(), curitem, check_match) != curitem) + { + std::string name{curitem->mName}; + size_t count{1}; + auto check_name = [&name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + do { + name = curitem->mName; + name += " #"; + name += std::to_string(++count); + } while(std::find_if(list.begin(), curitem, check_name) != curitem); + curitem->mName = std::move(name); + } + } + } +} + + struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; - int bufferSizeNotify(jack_nframes_t numframes) noexcept; - static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg) noexcept - { return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); } + int processRt(jack_nframes_t numframes) noexcept; + static int processRtC(jack_nframes_t numframes, void *arg) noexcept + { return static_cast<JackPlayback*>(arg)->processRt(numframes); } int process(jack_nframes_t numframes) noexcept; static int processC(jack_nframes_t numframes, void *arg) noexcept @@ -164,15 +295,21 @@ struct JackPlayback final : public BackendBase { int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; + std::string mPortPattern; + jack_client_t *mClient{nullptr}; - jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{}; + std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{}; + + std::mutex mMutex; + std::atomic<bool> mPlaying{false}; + bool mRTMixing{false}; RingBufferPtr mRing; al::semaphore mSem; @@ -187,31 +324,35 @@ JackPlayback::~JackPlayback() if(!mClient) return; - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); + mPort.fill(nullptr); + jack_client_close(mClient); mClient = nullptr; } -int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) noexcept +int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::lock_guard<std::mutex> _{mDevice->StateLock}; - mDevice->UpdateSize = numframes; - mDevice->BufferSize = numframes*2; - - const char *devname{mDevice->DeviceName.c_str()}; - ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; - - TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize); + std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out; + size_t numchans{0}; + for(auto port : mPort) + { + if(!port || numchans == mDevice->RealOut.Buffer.size()) + break; + out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes)); + } - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); + if(mPlaying.load(std::memory_order_acquire)) LIKELY + mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(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; } @@ -219,69 +360,64 @@ int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) noexcept int JackPlayback::process(jack_nframes_t numframes) noexcept { - jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS]; - ALsizei numchans{0}; + std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out; + size_t numchans{0}; for(auto port : mPort) { if(!port) break; out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes)); } - auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, static_cast<ALuint>(data.first.len))}; - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* + jack_nframes_t total{0}; + if(mPlaying.load(std::memory_order_acquire)) LIKELY + { + auto data = mRing->getReadVector(); + jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))}; + auto write_first = [&data,numchans,todo](float *outbuf) -> float* { - const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat + const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf); + auto deinterlace_input = [&in,numchans]() noexcept -> float + { + float ret{*in}; + in += numchans; + return ret; + }; + std::generate_n(outbuf, todo, deinterlace_input); + data.first.buf += sizeof(float); + return outbuf + todo; + }; + std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first); + total += todo; + + todo = minu(numframes-total, static_cast<uint>(data.second.len)); + if(todo > 0) + { + auto write_second = [&data,numchans,todo](float *outbuf) -> float* + { + const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf); + auto deinterlace_input = [&in,numchans]() noexcept -> float { - ALfloat ret{*in}; + float ret{*in}; in += numchans; return ret; - } - ); - data.first.buf += sizeof(ALfloat); - return outbuf + todo; + }; + std::generate_n(outbuf, todo, deinterlace_input); + data.second.buf += sizeof(float); + return outbuf + todo; + }; + std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second); + total += todo; } - ); - jack_nframes_t total{todo}; - todo = minu(numframes-total, static_cast<ALuint>(data.second.len)); - if(todo > 0) - { - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat - { - ALfloat ret{*in}; - in += numchans; - return ret; - } - ); - data.second.buf += sizeof(ALfloat); - return outbuf + todo; - } - ); - total += todo; + mRing->readAdvance(total); + mSem.post(); } - mRing->readAdvance(total); - mSem.post(); - if(numframes > total) { - todo = numframes-total; - std::transform(out, out+numchans, out, - [todo](ALfloat *outbuf) -> ALfloat* - { - std::fill_n(outbuf, todo, 0.0f); - return outbuf + todo; - } - ); + const jack_nframes_t todo{numframes - total}; + auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); }; + std::for_each(out.begin(), out.begin()+numchans, clear_buf); } return 0; @@ -292,28 +428,28 @@ int JackPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - std::unique_lock<JackPlayback> dlock{*this}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + const size_t frame_step{mDevice->channelsFromFmt()}; + + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() < mDevice->UpdateSize) { - dlock.unlock(); mSem.wait(); - dlock.lock(); continue; } auto data = mRing->getWriteVector(); - auto todo = static_cast<ALuint>(data.first.len + data.second.len); + size_t todo{data.first.len + data.second.len}; todo -= todo%mDevice->UpdateSize; - ALuint len1{minu(static_cast<ALuint>(data.first.len), todo)}; - ALuint len2{minu(static_cast<ALuint>(data.second.len), todo-len1)}; + const auto len1 = static_cast<uint>(minz(data.first.len, todo)); + const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1)); - aluMixData(mDevice, data.first.buf, len1); + std::lock_guard<std::mutex> _{mMutex}; + mDevice->renderSamples(data.first.buf, len1, frame_step); if(len2 > 0) - aluMixData(mDevice, data.second.buf, len2); + mDevice->renderSamples(data.second.buf, len2, frame_step); mRing->writeAdvance(todo); } @@ -321,77 +457,105 @@ int JackPlayback::mixerProc() } -void JackPlayback::open(const ALCchar *name) +void JackPlayback::open(const char *name) { - if(!name) - name = jackDevice; - else if(strcmp(name, jackDevice) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + if(!mClient) + { + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + + jack_status_t status; + mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); + if(mClient == nullptr) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to open client connection: 0x%02x", status}; + if((status&JackServerStarted)) + TRACE("JACK server started\n"); + if((status&JackNameNotUnique)) + { + client_name = jack_get_client_name(mClient); + TRACE("Client name not unique, got '%s' instead\n", client_name); + } + } - const char *client_name{"alsoft"}; - jack_status_t status; - mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); - if(mClient == nullptr) - throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open client connection: 0x%02x", - status}; - - if((status&JackServerStarted)) - TRACE("JACK server started\n"); - if((status&JackNameNotUnique)) + if(PlaybackList.empty()) + EnumerateDevices(mClient, PlaybackList); + + if(!name && !PlaybackList.empty()) { - client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got `%s' instead\n", client_name); + 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?name:""}; + mPortPattern = iter->mPattern; } - jack_set_process_callback(mClient, &JackPlayback::processC, this); - jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this); + mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); mDevice->DeviceName = name; } bool JackPlayback::reset() { - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); + mPort.fill(nullptr); /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; - - const char *devname{mDevice->DeviceName.c_str()}; - ALuint 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; - auto ports_end = std::begin(mPort) + mDevice->channelsFromFmt(); - auto bad_port = std::find_if_not(std::begin(mPort), ports_end, - [this](jack_port_t *&port) -> bool - { - std::string name{"channel_" + std::to_string(&port - mPort + 1)}; - port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - return port != nullptr; - } - ); + int port_num{0}; + auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); + auto bad_port = mPort.begin(); + while(bad_port != ports_end) + { + std::string name{"channel_" + std::to_string(++port_num)}; + *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + JackPortIsOutput | JackPortIsTerminal, 0); + if(!*bad_port) break; + ++bad_port; + } if(bad_port != ports_end) { - ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == std::begin(mPort)) return false; + ERR("Failed to register enough JACK ports for %s output\n", + DevFmtChannelsString(mDevice->FmtChans)); + if(bad_port == mPort.begin()) return false; - if(bad_port == std::begin(mPort)+1) + if(bad_port == mPort.begin()+1) mDevice->FmtChans = DevFmtMono; else { - ports_end = mPort+2; + ports_end = mPort.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); @@ -401,70 +565,87 @@ bool JackPlayback::reset() } } - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); return true; } -bool JackPlayback::start() +void JackPlayback::start() { if(jack_activate(mClient)) - { - ERR("Failed to activate client\n"); - return false; - } + throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const char **ports{jack_get_ports(mClient, nullptr, nullptr, - JackPortIsPhysical|JackPortIsInput)}; - if(ports == nullptr) + const char *devname{mDevice->DeviceName.c_str()}; + if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - ERR("No physical playback ports found\n"); - jack_deactivate(mClient); - return false; - } - std::mismatch(std::begin(mPort), std::end(mPort), ports, - [this](const jack_port_t *port, const char *pname) -> bool + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortIsInput)}; + if(!pnames) { - if(!port) return false; - if(!pname) + jack_deactivate(mClient); + throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; + } + + for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + { + if(!pnames[i]) { - ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); - return false; + ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); + break; } - if(jack_connect(mClient, jack_port_name(port), pname)) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port), - pname); - return true; + if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) + ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]), + pnames[i]); } - ); - jack_free(ports); - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; - return true; } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + + /* Reconfigure buffer metrics in case the server changed it since the reset + * (it won't change again after jack_activate), then allocate the ring + * buffer with the appropriate size. + */ + mDevice->Frequency = jack_get_sample_rate(mClient); + mDevice->UpdateSize = jack_get_buffer_size(mClient); + mDevice->BufferSize = mDevice->UpdateSize * 2; + + mRing = nullptr; + if(mRTMixing) + mPlaying.store(true, std::memory_order_release); + else + { + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; + bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + mDevice->BufferSize = bufsize + mDevice->UpdateSize; + + mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); + + try { + mPlaying.store(true, std::memory_order_release); + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; + } + catch(std::exception& e) { + jack_deactivate(mClient); + mPlaying.store(false, std::memory_order_release); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; + } } - jack_deactivate(mClient); - return false; } void JackPlayback::stop() { - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); + if(mPlaying.load(std::memory_order_acquire)) + { + mKillNow.store(true, std::memory_order_release); + if(mThread.joinable()) + { + mSem.post(); + mThread.join(); + } - jack_deactivate(mClient); + jack_deactivate(mClient); + mPlaying.store(false, std::memory_order_release); + } } @@ -472,9 +653,9 @@ ClockLatency JackPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard<JackPlayback> _{*this}; + std::lock_guard<std::mutex> _{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; @@ -493,13 +674,16 @@ bool JackBackendFactory::init() if(!jack_load()) return false; - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) + if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false)) ClientOptions = static_cast<jack_options_t>(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) { @@ -516,21 +700,37 @@ bool JackBackendFactory::init() bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -void JackBackendFactory::probe(DevProbe type, std::string *outnames) +std::string JackBackendFactory::probe(BackendType type) { - switch(type) + std::string outnames; + auto append_name = [&outnames](const DeviceEntry &entry) -> void { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(jackDevice, sizeof(jackDevice)); - break; + /* Includes null char. */ + outnames.append(entry.mName.c_str(), entry.mName.length()+1); + }; - case DevProbe::Capture: - break; + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + jack_status_t status; + switch(type) + { + case BackendType::Playback: + if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}) + { + EnumerateDevices(client, PlaybackList); + jack_client_close(client); + } + else + WARN("jack_client_open() failed, 0x%02x\n", status); + std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new JackPlayback{device}}; diff --git a/alc/backends/jack.h b/alc/backends/jack.h index 10beebfb..b83f24dd 100644 --- a/alc/backends/jack.h +++ b/alc/backends/jack.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_JACK_H #define BACKENDS_JACK_H -#include "backends/base.h" +#include "base.h" struct JackBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/loopback.cpp b/alc/backends/loopback.cpp index 511061f3..bf4ab246 100644 --- a/alc/backends/loopback.cpp +++ b/alc/backends/loopback.cpp @@ -20,39 +20,38 @@ #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; - bool start() override; + void start() override; void stop() override; DEF_NEWDEL(LoopbackBackend) }; -void LoopbackBackend::open(const ALCchar *name) +void LoopbackBackend::open(const char *name) { mDevice->DeviceName = name; } bool LoopbackBackend::reset() { - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); return true; } -bool LoopbackBackend::start() -{ return true; } +void LoopbackBackend::start() +{ } void LoopbackBackend::stop() { } @@ -66,10 +65,10 @@ bool LoopbackBackendFactory::init() bool LoopbackBackendFactory::querySupport(BackendType) { return true; } -void LoopbackBackendFactory::probe(DevProbe, std::string*) -{ } +std::string LoopbackBackendFactory::probe(BackendType) +{ return std::string{}; } -BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType) +BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } BackendFactory &LoopbackBackendFactory::getFactory() diff --git a/alc/backends/loopback.h b/alc/backends/loopback.h index 09c085b8..cb42b3c8 100644 --- a/alc/backends/loopback.h +++ b/alc/backends/loopback.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H -#include "backends/base.h" +#include "base.h" struct LoopbackBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/null.cpp b/alc/backends/null.cpp index bc2a0c9c..5a8fc255 100644 --- a/alc/backends/null.cpp +++ b/alc/backends/null.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/null.h" +#include "null.h" #include <exception> #include <atomic> @@ -30,11 +30,9 @@ #include <functional> #include <thread> -#include "alcmain.h" -#include "alexcpt.h" +#include "core/device.h" #include "almalloc.h" -#include "alu.h" -#include "logging.h" +#include "core/helpers.h" #include "threads.h" @@ -44,17 +42,17 @@ using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; -constexpr ALCchar nullDevice[] = "No Output"; +constexpr char nullDevice[] = "No Output"; struct NullBackend final : public BackendBase { - NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } + NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; std::atomic<bool> mKillNow{true}; @@ -72,8 +70,8 @@ int NullBackend::mixerProc() int64_t done{0}; auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); @@ -86,8 +84,7 @@ int NullBackend::mixerProc() } while(avail-done >= mDevice->UpdateSize) { - std::lock_guard<NullBackend> _{*this}; - aluMixData(mDevice, nullptr, mDevice->UpdateSize); + mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); done += mDevice->UpdateSize; } @@ -108,35 +105,33 @@ int NullBackend::mixerProc() } -void NullBackend::open(const ALCchar *name) +void NullBackend::open(const char *name) { if(!name) name = nullDevice; else if(strcmp(name, nullDevice) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; mDevice->DeviceName = name; } bool NullBackend::reset() { - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); return true; } -bool NullBackend::start() +void NullBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - return false; } void NullBackend::stop() @@ -155,20 +150,22 @@ bool NullBackendFactory::init() bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -void NullBackendFactory::probe(DevProbe type, std::string *outnames) +std::string NullBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(nullDevice, sizeof(nullDevice)); - break; - case DevProbe::Capture: - break; + case BackendType::Playback: + /* Includes null char. */ + outnames.append(nullDevice, sizeof(nullDevice)); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new NullBackend{device}}; diff --git a/alc/backends/null.h b/alc/backends/null.h index f19d5b4d..7048cad6 100644 --- a/alc/backends/null.h +++ b/alc/backends/null.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_NULL_H #define BACKENDS_NULL_H -#include "backends/base.h" +#include "base.h" struct NullBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/oboe.cpp b/alc/backends/oboe.cpp new file mode 100644 index 00000000..461f5a6a --- /dev/null +++ b/alc/backends/oboe.cpp @@ -0,0 +1,360 @@ + +#include "config.h" + +#include "oboe.h" + +#include <cassert> +#include <cstring> +#include <stdint.h> + +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" +#include "ringbuffer.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<uint32_t>(numFrames), + static_cast<uint32_t>(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.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); + builder.setSampleRate(static_cast<int32_t>(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: +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + format = oboe::AudioFormat::I32; + break; +#endif + 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<int32_t>(mDevice->BufferSize), + mStream->getBufferCapacityInFrames())); + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + if(static_cast<uint>(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; +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + case oboe::AudioFormat::I32: + mDevice->FmtType = DevFmtInt; + break; + case oboe::AudioFormat::I24: +#endif + 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<uint32_t>(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<uint32_t>(mStream->getFramesPerBurst())); + mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + static_cast<uint32_t>(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, public oboe::AudioStreamCallback { + OboeCapture(DeviceBase *device) : BackendBase{device} { } + + oboe::ManagedStream mStream; + + RingBufferPtr mRing{nullptr}; + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, + int32_t numFrames) override; + + void open(const char *name) override; + void start() override; + void stop() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; +}; + +oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData, + int32_t numFrames) +{ + mRing->write(audioData, static_cast<uint32_t>(numFrames)); + return oboe::DataCallbackResult::Continue; +} + + +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) + ->setSampleRate(static_cast<int32_t>(mDevice->Frequency)) + ->setCallback(this); + /* 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 DevFmtX714: + case DevFmtX3D71: + 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 + * convert. + */ + switch(mDevice->FmtType) + { + case DevFmtShort: + builder.setFormat(oboe::AudioFormat::I16); + break; + case DevFmtFloat: + builder.setFormat(oboe::AudioFormat::Float); + break; + case DevFmtInt: +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + builder.setFormat(oboe::AudioFormat::I32); + break; +#endif + case DevFmtByte: + case DevFmtUByte: + case DevFmtUShort: + 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)}; + + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + /* Ensure a minimum ringbuffer size of 100ms. */ + mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10), + static_cast<uint32_t>(mStream->getBytesPerFrame()), false); + + 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() +{ + 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() +{ return static_cast<uint>(mRing->readSpace()); } + +void OboeCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, 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/alc/backends/oboe.h b/alc/backends/oboe.h new file mode 100644 index 00000000..a39c2445 --- /dev/null +++ b/alc/backends/oboe.h @@ -0,0 +1,19 @@ +#ifndef BACKENDS_OBOE_H +#define BACKENDS_OBOE_H + +#include "base.h" + +struct OboeBackendFactory final : public BackendFactory { +public: + bool init() override; + + bool querySupport(BackendType type) override; + + std::string probe(BackendType type) override; + + BackendPtr createBackend(DeviceBase *device, BackendType type) override; + + static BackendFactory &getFactory(); +}; + +#endif /* BACKENDS_OBOE_H */ diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp index a1fdccc7..f5b98fb8 100644 --- a/alc/backends/opensl.cpp +++ b/alc/backends/opensl.cpp @@ -21,7 +21,7 @@ #include "config.h" -#include "backends/opensl.h" +#include "opensl.h" #include <stdlib.h> #include <jni.h> @@ -32,11 +32,12 @@ #include <thread> #include <functional> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" -#include "compat.h" -#include "endiantest.h" +#include "albit.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "opthelpers.h" #include "ringbuffer.h" #include "threads.h" @@ -53,10 +54,10 @@ namespace { #define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS -constexpr ALCchar opensl_device[] = "OpenSL"; +constexpr char opensl_device[] = "OpenSL"; -SLuint32 GetChannelMask(DevFmtChannels chans) +constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept { switch(chans) { @@ -67,15 +68,18 @@ SLuint32 GetChannelMask(DevFmtChannels chans) case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; - case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | - SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | - SL_SPEAKER_BACK_RIGHT; case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; - case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + case DevFmtX71: + case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; + case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | + SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT | + SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT | + SL_SPEAKER_TOP_BACK_RIGHT; case DevFmtAmbi3D: break; } @@ -83,7 +87,7 @@ SLuint32 GetChannelMask(DevFmtChannels chans) } #ifdef SL_ANDROID_DATAFORMAT_PCM_EX -SLuint32 GetTypeRepresentation(DevFmtType type) +constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept { switch(type) { @@ -102,7 +106,14 @@ SLuint32 GetTypeRepresentation(DevFmtType type) } #endif -const char *res_str(SLresult result) +constexpr SLuint32 GetByteOrderEndianness() noexcept +{ + if(al::endian::native == al::endian::little) + return SL_BYTEORDER_LITTLEENDIAN; + return SL_BYTEORDER_BIGENDIAN; +} + +constexpr const char *res_str(SLresult result) noexcept { switch(result) { @@ -136,14 +147,15 @@ const char *res_str(SLresult result) return "Unknown error code"; } -#define PRINTERR(x, s) do { \ - if UNLIKELY((x) != SL_RESULT_SUCCESS) \ - ERR("%s: %s\n", (s), res_str((x))); \ -} while(0) +inline void PrintErr(SLresult res, const char *str) +{ + if(res != SL_RESULT_SUCCESS) UNLIKELY + ERR("%s: %s\n", str, res_str(res)); +} struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -152,9 +164,9 @@ struct OpenSLPlayback final : public BackendBase { int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; @@ -171,7 +183,9 @@ struct OpenSLPlayback final : public BackendBase { RingBufferPtr mRing{nullptr}; al::semaphore mSem; - ALuint mFrameSize{0}; + std::mutex mMutex; + + uint mFrameSize{0}; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -221,55 +235,56 @@ int OpenSLPlayback::mixerProc() SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY"); } - std::unique_lock<OpenSLPlayback> dlock{*this}; + const size_t frame_step{mDevice->channelsFromFmt()}; + if(SL_RESULT_SUCCESS != result) - aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result); + mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result); - while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() == 0) { SLuint32 state{0}; result = VCALL(player,GetPlayState)(&state); - PRINTERR(result, "player->GetPlayState"); + PrintErr(result, "player->GetPlayState"); if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING); - PRINTERR(result, "player->SetPlayState"); + PrintErr(result, "player->SetPlayState"); } if(SL_RESULT_SUCCESS != result) { - aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result); + mDevice->handleDisconnect("Failed to start playback: 0x%08x", result); break; } if(mRing->writeSpace() == 0) { - dlock.unlock(); mSem.wait(); - dlock.lock(); continue; } } + std::unique_lock<std::mutex> dlock{mMutex}; auto data = mRing->getWriteVector(); - aluMixData(mDevice, data.first.buf, - static_cast<ALuint>(data.first.len*mDevice->UpdateSize)); + mDevice->renderSamples(data.first.buf, + static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step); if(data.second.len > 0) - aluMixData(mDevice, data.second.buf, - static_cast<ALuint>(data.second.len*mDevice->UpdateSize)); + mDevice->renderSamples(data.second.buf, + static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step); size_t todo{data.first.len + data.second.len}; mRing->writeAdvance(todo); + dlock.unlock(); for(size_t i{0};i < todo;i++) { @@ -281,10 +296,10 @@ int OpenSLPlayback::mixerProc() } result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { - aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result); + mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); break; } @@ -297,35 +312,39 @@ int OpenSLPlayback::mixerProc() } -void OpenSLPlayback::open(const ALCchar *name) +void OpenSLPlayback::open(const char *name) { if(!name) name = opensl_device; else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* 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"); + PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); + PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); + PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr); - PRINTERR(result, "engine->CreateOutputMix"); + PrintErr(result, "engine->CreateOutputMix"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "outputMix->Realize"); + PrintErr(result, "outputMix->Realize"); } if(SL_RESULT_SUCCESS != result) @@ -339,7 +358,7 @@ void OpenSLPlayback::open(const ALCchar *name) mEngineObj = nullptr; mEngine = nullptr; - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, "Failed to initialize OpenSL device: 0x%08x", result}; } @@ -427,7 +446,7 @@ bool OpenSLPlayback::reset() mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); mFrameSize = mDevice->frameSizeFromFmt(); @@ -455,7 +474,7 @@ bool OpenSLPlayback::reset() format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; + format_pcm_ex.endianness = GetByteOrderEndianness(); format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); audioSrc.pLocator = &loc_bufq; @@ -486,28 +505,27 @@ bool OpenSLPlayback::reset() format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; + format_pcm.endianness = GetByteOrderEndianness(); audioSrc.pLocator = &loc_bufq; audioSrc.pFormat = &format_pcm; result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); - PRINTERR(result, "engine->CreateAudioPlayer"); + PrintErr(result, "engine->CreateAudioPlayer"); } if(SL_RESULT_SUCCESS == result) { /* Set the stream type to "media" (games, music, etc), if possible. */ SLAndroidConfigurationItf config; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLint32 streamType = SL_ANDROID_STREAM_MEDIA; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(streamType)); - PRINTERR(result, "config->SetConfiguration"); + PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ @@ -516,12 +534,12 @@ bool OpenSLPlayback::reset() if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "bufferQueue->Realize"); + PrintErr(result, "bufferQueue->Realize"); } if(SL_RESULT_SUCCESS == result) { - const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true); + const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true); } if(SL_RESULT_SUCCESS != result) @@ -536,32 +554,31 @@ bool OpenSLPlayback::reset() return true; } -bool OpenSLPlayback::start() +void OpenSLPlayback::start() { mRing->reset(); SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); + if(SL_RESULT_SUCCESS == result) + { + result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); + PrintErr(result, "bufferQueue->RegisterCallback"); + } if(SL_RESULT_SUCCESS != result) - return false; - - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - if(SL_RESULT_SUCCESS != result) return false; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to register callback: 0x%08x", result}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); - return true; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - return false; } void OpenSLPlayback::stop() @@ -574,25 +591,25 @@ void OpenSLPlayback::stop() SLPlayItf player; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)}; - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); - PRINTERR(result, "player->SetPlayState"); + PrintErr(result, "player->SetPlayState"); } SLAndroidSimpleBufferQueueItf bufferQueue; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL0(bufferQueue,Clear)(); - PRINTERR(result, "bufferQueue->Clear"); + PrintErr(result, "bufferQueue->Clear"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr); - PRINTERR(result, "bufferQueue->RegisterCallback"); + PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { @@ -601,7 +618,9 @@ void OpenSLPlayback::stop() std::this_thread::yield(); result = VCALL(bufferQueue,GetState)(&state); } while(SL_RESULT_SUCCESS == result && state.count > 0); - PRINTERR(result, "bufferQueue->GetState"); + PrintErr(result, "bufferQueue->GetState"); + + mRing->reset(); } } @@ -609,7 +628,7 @@ ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard<OpenSLPlayback> _{*this}; + std::lock_guard<std::mutex> _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -619,18 +638,18 @@ 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; static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept { static_cast<OpenSLCapture*>(context)->process(bq); } - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; @@ -640,9 +659,9 @@ struct OpenSLCapture final : public BackendBase { SLObjectItf mRecordObj{nullptr}; RingBufferPtr mRing{nullptr}; - ALCuint mSplOffset{0u}; + uint mSplOffset{0u}; - ALuint mFrameSize{0}; + uint mFrameSize{0}; DEF_NEWDEL(OpenSLCapture) }; @@ -667,39 +686,40 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept } -void OpenSLCapture::open(const ALCchar* name) +void OpenSLCapture::open(const char* name) { if(!name) name = opensl_device; else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); + PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); + PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); + PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - ALuint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; + uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - ALuint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, + uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, mDevice->Frequency/100*5)}; - ALuint num_updates{(length+update_len-1) / update_len}; + uint num_updates{(length+update_len-1) / update_len}; - mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false); + mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); mDevice->UpdateSize = update_len; - mDevice->BufferSize = static_cast<ALuint>(mRing->writeSpace() * update_len); + mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { @@ -729,8 +749,7 @@ void OpenSLCapture::open(const ALCchar* name) format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; + format_pcm_ex.endianness = GetByteOrderEndianness(); format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); audioSnk.pLocator = &loc_bq; @@ -753,15 +772,14 @@ void OpenSLCapture::open(const ALCchar* name) format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; + format_pcm.endianness = GetByteOrderEndianness(); audioSnk.pLocator = &loc_bq; audioSnk.pFormat = &format_pcm; result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); } - PRINTERR(result, "engine->CreateAudioRecorder"); + PrintErr(result, "engine->CreateAudioRecorder"); } } if(SL_RESULT_SUCCESS == result) @@ -769,13 +787,13 @@ void OpenSLCapture::open(const ALCchar* name) /* Set the record preset to "generic", if possible. */ SLAndroidConfigurationItf config; result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); + PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset, sizeof(preset)); - PRINTERR(result, "config->SetConfiguration"); + PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ @@ -784,34 +802,37 @@ void OpenSLCapture::open(const ALCchar* name) if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "recordObj->Realize"); + PrintErr(result, "recordObj->Realize"); } SLAndroidSimpleBufferQueueItf bufferQueue; if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); + PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { - const ALuint chunk_size{mDevice->UpdateSize * mFrameSize}; + const uint chunk_size{mDevice->UpdateSize * mFrameSize}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; auto data = mRing->getWriteVector(); + std::fill_n(data.first.buf, data.first.len*chunk_size, silence); + std::fill_n(data.second.buf, data.second.len*chunk_size, silence); for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } } @@ -826,108 +847,126 @@ void OpenSLCapture::open(const ALCchar* name) mEngineObj = nullptr; mEngine = nullptr; - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, "Failed to initialize OpenSL device: 0x%08x", result}; } mDevice->DeviceName = name; } -bool OpenSLCapture::start() +void OpenSLCapture::start() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); - PRINTERR(result, "record->SetRecordState"); + PrintErr(result, "record->SetRecordState"); } - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result); - return false; - } - - return true; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start capture: 0x%08x", result}; } void OpenSLCapture::stop() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED); - PRINTERR(result, "record->SetRecordState"); + PrintErr(result, "record->SetRecordState"); } } -ALCenum OpenSLCapture::captureSamples(al::byte *buffer, ALCuint samples) +void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) { - SLAndroidSimpleBufferQueueItf bufferQueue{}; - if LIKELY(mDevice->Connected.load(std::memory_order_acquire)) - { - const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bufferQueue)}; - PRINTERR(result, "recordObj->GetInterface"); - if UNLIKELY(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to get capture buffer queue: 0x%08x", result); - bufferQueue = nullptr; - } - } - - const ALuint update_size{mDevice->UpdateSize}; - const ALuint chunk_size{update_size * mFrameSize}; + const uint update_size{mDevice->UpdateSize}; + const uint chunk_size{update_size * mFrameSize}; /* Read the desired samples from the ring buffer then advance its read * pointer. */ - auto data = mRing->getReadVector(); - for(ALCuint i{0};i < samples;) + size_t adv_count{0}; + auto rdata = mRing->getReadVector(); + for(uint i{0};i < samples;) { - const ALCuint rem{minu(samples - i, update_size - mSplOffset)}; - std::copy_n(data.first.buf + mSplOffset*mFrameSize, rem*mFrameSize, buffer + i*mFrameSize); + const uint rem{minu(samples - i, update_size - mSplOffset)}; + std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, + buffer + i*size_t{mFrameSize}); mSplOffset += rem; if(mSplOffset == update_size) { /* Finished a chunk, reset the offset and advance the read pointer. */ mSplOffset = 0; - mRing->readAdvance(1); - - if LIKELY(bufferQueue) - { - const SLresult result{VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size)}; - PRINTERR(result, "bufferQueue->Enqueue"); - if UNLIKELY(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", - result); - bufferQueue = nullptr; - } - } - data.first.len--; - if(!data.first.len) - data.first = data.second; + ++adv_count; + rdata.first.len -= 1; + if(!rdata.first.len) + rdata.first = rdata.second; else - data.first.buf += chunk_size; + rdata.first.buf += chunk_size; } i += rem; } - return ALC_NO_ERROR; + SLAndroidSimpleBufferQueueItf bufferQueue{}; + if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY + { + const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &bufferQueue)}; + PrintErr(result, "recordObj->GetInterface"); + if(SL_RESULT_SUCCESS != result) UNLIKELY + { + mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); + bufferQueue = nullptr; + } + } + if(!bufferQueue || adv_count == 0) + return; + + /* For each buffer chunk that was fully read, queue another writable buffer + * chunk to keep the OpenSL queue full. This is rather convulated, as a + * result of the ring buffer holding more elements than are writable at a + * given time. The end of the write vector increments when the read pointer + * advances, which will "expose" a previously unwritable element. So for + * every element that we've finished reading, we queue that many elements + * from the end of the write vector. + */ + mRing->readAdvance(adv_count); + + SLresult result{SL_RESULT_SUCCESS}; + auto wdata = mRing->getWriteVector(); + if(adv_count > wdata.second.len) LIKELY + { + auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len); + auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1); + for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) + { + result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); + PrintErr(result, "bufferQueue->Enqueue"); + } + } + if(wdata.second.len > 0) + { + auto len2 = std::min(wdata.second.len, adv_count); + auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2); + for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) + { + result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); + PrintErr(result, "bufferQueue->Enqueue"); + } + } } -ALCuint OpenSLCapture::availableSamples() -{ return static_cast<ALuint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } +uint OpenSLCapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } } // namespace @@ -936,19 +975,21 @@ bool OSLBackendFactory::init() { return true; } bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void OSLBackendFactory::probe(DevProbe type, std::string *outnames) +std::string OSLBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(opensl_device, sizeof(opensl_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(opensl_device, sizeof(opensl_device)); + break; } + return outnames; } -BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OpenSLPlayback{device}}; diff --git a/alc/backends/opensl.h b/alc/backends/opensl.h index 809aa339..b8162447 100644 --- a/alc/backends/opensl.h +++ b/alc/backends/opensl.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSL_H #define BACKENDS_OSL_H -#include "backends/base.h" +#include "base.h" struct OSLBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/oss.cpp b/alc/backends/oss.cpp index 59cc44e4..6d4fa261 100644 --- a/alc/backends/oss.cpp +++ b/alc/backends/oss.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/oss.h" +#include "oss.h" #include <fcntl.h> #include <poll.h> @@ -41,16 +41,14 @@ #include <thread> #include <utility> -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "alexcpt.h" +#include "albyte.h" +#include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" -#include "alu.h" -#include "logging.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" #include "threads.h" #include "vector.h" @@ -94,14 +92,6 @@ struct DevMap { std::string device_name; }; -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - al::vector<DevMap> PlaybackDevices; al::vector<DevMap> CaptureDevices; @@ -110,60 +100,59 @@ al::vector<DevMap> CaptureDevices; #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector<DevMap> *devlist, int type) +void ALCossListPopulate(al::vector<DevMap> &devlist, int type) { - devlist->emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); + devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); } #else -void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen) +void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path) { #ifdef ALC_OSS_DEVNODE_TRUC - for(size_t i{0};i < plen;i++) + for(size_t i{0};i < path.size();++i) { - if(path[i] == '.') + if(path[i] == '.' && handle.size() + i >= path.size()) { - if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0) - hlen = hlen + i - plen; - plen = i; + const size_t hoffset{handle.size() + i - path.size()}; + if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0) + handle = handle.first(hoffset); + path = path.first(i); } } #endif - if(handle[0] == '\0') - { + if(handle.empty()) handle = path; - hlen = plen; - } - std::string basename{handle, hlen}; - basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end()); - std::string devname{path, plen}; - devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end()); + std::string basename{handle.data(), handle.size()}; + std::string devname{path.data(), path.size()}; - auto iter = std::find_if(list->cbegin(), list->cend(), - [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; } - ); - if(iter != list->cend()) + auto match_devname = [&devname](const DevMap &entry) -> bool + { return entry.device_name == devname; }; + if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend()) return; + auto checkName = [&list](const std::string &name) -> bool + { + auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); + }; int count{1}; std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) + while(checkName(newname)) { newname = basename; newname += " #"; newname += std::to_string(++count); } - list->emplace_back(DevMap{std::move(newname), std::move(devname)}); - const DevMap &entry = list->back(); + list.emplace_back(DevMap{std::move(newname), std::move(devname)}); + const DevMap &entry = list.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } -void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag) +void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag) { int fd{open("/dev/mixer", O_RDONLY)}; if(fd < 0) @@ -191,21 +180,14 @@ void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag) if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') continue; - const char *handle; - size_t len; + al::span<const char> handle; if(ai.handle[0] != '\0') - { - len = strnlen(ai.handle, sizeof(ai.handle)); - handle = ai.handle; - } + handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))}; else - { - len = strnlen(ai.name, sizeof(ai.name)); - handle = ai.name; - } + handle = {ai.name, strnlen(ai.name, sizeof(ai.name))}; + al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; - ALCossListAppend(devlist, handle, len, ai.devnode, - strnlen(ai.devnode, sizeof(ai.devnode))); + ALCossListAppend(devlist, handle, devnode); } done: @@ -214,26 +196,26 @@ done: fd = -1; const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; - auto iter = std::find_if(devlist->cbegin(), devlist->cend(), + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [defdev](const DevMap &entry) -> bool { return entry.device_name == defdev; } ); - if(iter == devlist->cend()) - devlist->insert(devlist->begin(), DevMap{DefaultName, defdev}); + if(iter == devlist.cend()) + devlist.insert(devlist.begin(), DevMap{DefaultName, defdev}); else { DevMap entry{std::move(*iter)}; - devlist->erase(iter); - devlist->insert(devlist->begin(), std::move(entry)); + devlist.erase(iter); + devlist.insert(devlist.begin(), std::move(entry)); } - devlist->shrink_to_fit(); + devlist.shrink_to_fit(); } #endif -ALCuint log2i(ALCuint x) +uint log2i(uint x) { - ALCuint y{0}; + uint y{0}; while(x > 1) { x >>= 1; @@ -244,19 +226,19 @@ ALCuint log2i(ALCuint x) struct OSSPlayback final : public BackendBase { - OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; int mFd{-1}; - al::vector<ALubyte> mMixData; + al::vector<al::byte> mMixData; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -267,7 +249,7 @@ struct OSSPlayback final : public BackendBase { OSSPlayback::~OSSPlayback() { if(mFd != -1) - close(mFd); + ::close(mFd); mFd = -1; } @@ -277,25 +259,23 @@ int OSSPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALuint frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_step{mDevice->channelsFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; - std::unique_lock<OSSPlayback> dlock{*this}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; - dlock.unlock(); int pret{poll(&pollitem, 1, 1000)}; - dlock.lock(); if(pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno)); + mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno)); break; } else if(pret == 0) @@ -304,9 +284,9 @@ int OSSPlayback::mixerProc() continue; } - ALubyte *write_ptr{mMixData.data()}; + al::byte *write_ptr{mMixData.data()}; size_t to_write{mMixData.size()}; - aluMixData(mDevice, write_ptr, static_cast<ALuint>(to_write/frame_size)); + mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, write_ptr, to_write)}; @@ -315,8 +295,7 @@ int OSSPlayback::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed writing playback samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno)); break; } @@ -329,7 +308,7 @@ int OSSPlayback::mixerProc() } -void OSSPlayback::open(const ALCchar *name) +void OSSPlayback::open(const char *name) { const char *devname{DefaultPlayback.c_str()}; if(!name) @@ -337,22 +316,27 @@ void OSSPlayback::open(const ALCchar *name) else { if(PlaybackDevices.empty()) - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); + ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname, + 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; } @@ -378,13 +362,13 @@ bool OSSPlayback::reset() break; } - ALuint periods{mDevice->BufferSize / mDevice->UpdateSize}; - ALuint numChannels{mDevice->channelsFromFmt()}; - ALuint ossSpeed{mDevice->Frequency}; - ALuint frameSize{numChannels * mDevice->bytesFromFmt()}; + uint periods{mDevice->BufferSize / mDevice->UpdateSize}; + uint numChannels{mDevice->channelsFromFmt()}; + uint ossSpeed{mDevice->Frequency}; + uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - ALuint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)}; - ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; + uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)}; + uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; const char *err; @@ -424,29 +408,26 @@ bool OSSPlayback::reset() } mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = static_cast<ALuint>(info.fragsize) / frameSize; - mDevice->BufferSize = static_cast<ALuint>(info.fragments) * mDevice->UpdateSize; + mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize; + mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); return true; } -bool OSSPlayback::start() +void OSSPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return false; } void OSSPlayback::stop() @@ -461,16 +442,16 @@ void OSSPlayback::stop() struct OSScapture final : public BackendBase { - OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } + OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; int mFd{-1}; @@ -495,7 +476,7 @@ int OSScapture::recordProc() SetRTPriority(); althrd_setname(RECORD_THREAD_NAME); - const ALuint frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) { pollfd pollitem{}; @@ -508,7 +489,7 @@ int OSScapture::recordProc() if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno)); + mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno)); break; } else if(sret == 0) @@ -524,11 +505,10 @@ int OSScapture::recordProc() if(amt < 0) { ERR("read failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed reading capture samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno)); break; } - mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size); + mRing->writeAdvance(static_cast<size_t>(amt)/frame_size); } } @@ -536,7 +516,7 @@ int OSScapture::recordProc() } -void OSScapture::open(const ALCchar *name) +void OSScapture::open(const char *name) { const char *devname{DefaultCapture.c_str()}; if(!name) @@ -544,20 +524,21 @@ void OSScapture::open(const ALCchar *name) else { if(CaptureDevices.empty()) - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); + ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; devname = iter->device_name.c_str(); } mFd = ::open(devname, O_RDONLY); if(mFd == -1) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname, + throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, strerror(errno)}; int ossFormat{}; @@ -576,21 +557,22 @@ void OSScapture::open(const ALCchar *name) case DevFmtInt: case DevFmtUInt: case DevFmtFloat: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - ALuint periods{4}; - ALuint numChannels{mDevice->channelsFromFmt()}; - ALuint frameSize{numChannels * mDevice->bytesFromFmt()}; - ALuint ossSpeed{mDevice->Frequency}; + uint periods{4}; + uint numChannels{mDevice->channelsFromFmt()}; + uint frameSize{numChannels * mDevice->bytesFromFmt()}; + uint ossSpeed{mDevice->Frequency}; /* according to the OSS spec, 16 bytes are the minimum */ - ALuint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)}; - ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; + uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)}; + uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ - throw al::backend_exception{ALC_INVALID_VALUE, #func " failed: %s", strerror(errno)}; \ + throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ + strerror(errno)}; \ } CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); @@ -600,35 +582,32 @@ void OSScapture::open(const ALCchar *name) #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels}; if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType), ossFormat}; - mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false); + mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false); mDevice->DeviceName = name; } -bool OSScapture::start() +void OSScapture::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; - return true; } catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording thread: %s", e.what()}; } - catch(...) { - } - return false; } void OSScapture::stop() @@ -641,14 +620,11 @@ void OSScapture::stop() ERR("Error resetting device: %s\n", strerror(errno)); } -ALCenum OSScapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void OSScapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint OSScapture::availableSamples() -{ return static_cast<ALCuint>(mRing->readSpace()); } +uint OSScapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()); } } // namespace @@ -672,37 +648,39 @@ bool OSSBackendFactory::init() bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void OSSBackendFactory::probe(DevProbe type, std::string *outnames) +std::string OSSBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { -#ifdef HAVE_STAT struct stat buf; if(stat(entry.device_name.c_str(), &buf) == 0) -#endif { /* Includes null char. */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); } }; switch(type) { - case DevProbe::Playback: - PlaybackDevices.clear(); - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices.clear(); + ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; - case DevProbe::Capture: - CaptureDevices.clear(); - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Capture: + CaptureDevices.clear(); + ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + + return outnames; } -BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OSSPlayback{device}}; diff --git a/alc/backends/oss.h b/alc/backends/oss.h index 9e63d7b6..4f2c00b9 100644 --- a/alc/backends/oss.h +++ b/alc/backends/oss.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSS_H #define BACKENDS_OSS_H -#include "backends/base.h" +#include "base.h" struct OSSBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/pipewire.cpp b/alc/backends/pipewire.cpp new file mode 100644 index 00000000..c6569a74 --- /dev/null +++ b/alc/backends/pipewire.cpp @@ -0,0 +1,2166 @@ +/** + * 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 <algorithm> +#include <atomic> +#include <cstring> +#include <cerrno> +#include <chrono> +#include <ctime> +#include <list> +#include <memory> +#include <mutex> +#include <stdint.h> +#include <thread> +#include <type_traits> +#include <utility> + +#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). GCC + * doesn't support ignoring -Weverything, so we have the list the individual + * warnings to ignore (and ignoring -Winline doesn't seem to work). + */ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wpedantic\"") +_Pragma("GCC diagnostic ignored \"-Wconversion\"") +_Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") +_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") +_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +_Pragma("GCC diagnostic ignored \"-Wsign-compare\"") +_Pragma("GCC diagnostic ignored \"-Winline\"") +_Pragma("GCC diagnostic ignored \"-Wpragmas\"") +_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<typename ...Args> +auto ppw_core_add_listener(pw_core *core, Args&& ...args) +{ return pw_core_add_listener(core, std::forward<Args>(args)...); } +template<typename ...Args> +auto ppw_core_sync(pw_core *core, Args&& ...args) +{ return pw_core_sync(core, std::forward<Args>(args)...); } +template<typename ...Args> +auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args) +{ return pw_registry_add_listener(reg, std::forward<Args>(args)...); } +template<typename ...Args> +auto ppw_node_add_listener(pw_node *node, Args&& ...args) +{ return pw_node_add_listener(node, std::forward<Args>(args)...); } +template<typename ...Args> +auto ppw_node_subscribe_params(pw_node *node, Args&& ...args) +{ return pw_node_subscribe_params(node, std::forward<Args>(args)...); } +template<typename ...Args> +auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args) +{ return pw_metadata_add_listener(mdata, std::forward<Args>(args)...); } + + +constexpr auto get_pod_type(const spa_pod *pod) noexcept +{ return SPA_POD_TYPE(pod); } + +template<typename T> +constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept +{ return al::span<T>{static_cast<T*>(SPA_POD_BODY(pod)), count}; } +template<typename T, size_t N> +constexpr auto get_pod_body(const spa_pod *pod) noexcept +{ return al::span<T,N>{static_cast<T*>(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 { + +/* Added in 0.3.33, but we currently only require 0.3.23. */ +#ifndef PW_KEY_NODE_RATE +#define PW_KEY_NODE_RATE "node.rate" +#endif + +using std::chrono::seconds; +using std::chrono::milliseconds; +using std::chrono::nanoseconds; +using uint = unsigned int; + +constexpr char pwireDevice[] = "PipeWire Output"; +constexpr char pwireInput[] = "PipeWire Input"; + + +bool check_version(const char *version) +{ + /* There doesn't seem to be a function to get the version as an integer, so + * instead we have to parse the string, which hopefully won't break in the + * future. + */ + int major{0}, minor{0}, revision{0}; + int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; + if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) + || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO))) + return true; + return false; +} + +#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_get_library_version) \ + 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_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) +#if PW_CHECK_VERSION(0,3,50) +#define PWIRE_FUNCS2(MAGIC) \ + MAGIC(pw_stream_get_time_n) +#else +#define PWIRE_FUNCS2(MAGIC) \ + MAGIC(pw_stream_get_time) +#endif + +void *pwire_handle; +#define MAKE_FUNC(f) decltype(f) * p##f; +PWIRE_FUNCS(MAKE_FUNC) +PWIRE_FUNCS2(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<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ +} while(0); + PWIRE_FUNCS(LOAD_FUNC) + PWIRE_FUNCS2(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_get_library_version ppw_get_library_version +#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_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 +#if PW_CHECK_VERSION(0,3,50) +#define pw_stream_get_time_n ppw_stream_get_time_n +#else +inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/) +{ return ppw_stream_get_time(stream, ptime); } +#endif +#endif + +#else + +constexpr bool pwire_load() { return true; } +#endif + +/* Helpers for retrieving values from params */ +template<uint32_t T> struct PodInfo { }; + +template<> +struct PodInfo<SPA_TYPE_Int> { + 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<SPA_TYPE_Id> { + using Type = uint32_t; + static auto get_value(const spa_pod *pod, uint32_t *val) + { return spa_pod_get_id(pod, val); } +}; + +template<uint32_t T> +using Pod_t = typename PodInfo<T>::Type; + +template<uint32_t T> +al::span<const Pod_t<T>> 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<const Pod_t<T>*>(v), nvals}; + } + return {}; +} + +template<uint32_t T> +al::optional<Pod_t<T>> get_value(const spa_pod *value) +{ + Pod_t<T> val{}; + if(PodInfo<T>::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<typename To, typename From> +To as(From) noexcept = delete; + +/* pw_proxy + * - pw_registry + * - pw_node + * - pw_metadata + */ +template<> +pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); } +template<> +pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); } +template<> +pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); } + + +struct PwContextDeleter { + void operator()(pw_context *context) const { pw_context_destroy(context); } +}; +using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>; + +struct PwCoreDeleter { + void operator()(pw_core *core) const { pw_core_disconnect(core); } +}; +using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>; + +struct PwRegistryDeleter { + void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); } +}; +using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>; + +struct PwNodeDeleter { + void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); } +}; +using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>; + +struct PwMetadataDeleter { + void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); } +}; +using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>; + +struct PwStreamDeleter { + void operator()(pw_stream *stream) const { pw_stream_destroy(stream); } +}; +using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>; + +/* Enums for bitflags... again... *sigh* */ +constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept +{ return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); } + +constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept +{ lhs = lhs | rhs; return lhs; } + +class ThreadMainloop { + pw_thread_loop *mLoop{}; + +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<ThreadMainloop> { + using std::unique_lock<ThreadMainloop>::unique_lock; + MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; + + auto wait() const -> void + { pw_thread_loop_wait(mutex()->mLoop); } + + template<typename Predicate> + auto wait(Predicate done_waiting) const -> void + { while(!done_waiting()) wait(); } +}; +using MainloopLockGuard = std::lock_guard<ThreadMainloop>; + + +/* 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<NodeProxy*> 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<bool> mInitDone{false}; + std::atomic<bool> 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(!mInitDone.load(std::memory_order_acquire)) UNLIKELY + { + 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<EventManager*>(object)->addCallback(id, permissions, type, version, props); } + + void removeCallback(uint32_t id); + static void removeCallbackC(void *object, uint32_t id) + { static_cast<EventManager*>(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<EventManager*>(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<EventManager>; +using EventWatcherLockGuard = std::lock_guard<EventManager>; + +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 { + uint32_t mId{}; + + uint64_t mSerial{}; + std::string mName; + std::string mDevName; + + NodeType mType{}; + bool mIsHeadphones{}; + bool mIs51Rear{}; + + uint mSampleRate{}; + DevFmtChannels mChannels{InvalidChannelConfig}; + + static std::vector<DeviceNode> sList; + static DeviceNode &Add(uint32_t id); + static DeviceNode *Find(uint32_t id); + static void Remove(uint32_t id); + static std::vector<DeviceNode> &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> 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 "<unknown>"; +} + +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 al::to_address(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 +}, X714Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, + SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR +}; + +/** + * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal + * to or a superset of map1). + */ +template<size_t N> +bool MatchChannelMap(const al::span<const uint32_t> 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<int32_t,3>(value); + + /* [0] is the default, [1] is the min, and [2] is the max. */ + TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0], + srates[1], srates[2]); + mSampleRate = static_cast<uint>(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<int32_t>(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 %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str()); + /* Pick the first rate listed that's within the allowed range (default + * rate if possible). + */ + for(const auto &rate : srates) + { + if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) + { + mSampleRate = static_cast<uint>(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<int32_t,1>(value); + + TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]); + mSampleRate = static_cast<uint>(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<SPA_TYPE_Id>(value); + if(chanmap.empty()) return; + + mIs51Rear = false; + + if(MatchChannelMap(chanmap, X714Map)) + mChannels = DevFmtX714; + else 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 %" PRIu64 " got %zu position%s for %s%s\n", mSerial, 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<SPA_TYPE_Int>(value); + if(!chancount) return; + + mIs51Rear = false; + + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *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 AudioSourceVirtualClass[]{"Audio/Source/Virtual"}; +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<NodeProxy*>(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<NodeProxy*>(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(!media_class) UNLIKELY return; + + NodeType ntype{}; + if(al::strcasecmp(media_class, AudioSinkClass) == 0) + ntype = NodeType::Sink; + else if(al::strcasecmp(media_class, AudioSourceClass) == 0 + || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0) + ntype = NodeType::Source; + else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) + ntype = NodeType::Duplex; + 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; + + uint64_t serial_id{info->id}; +#ifdef PW_KEY_OBJECT_SERIAL + if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}) + { + char *serial_end{}; + serial_id = std::strtoull(serial_str, &serial_end, 0); + if(*serial_end != '\0' || errno == ERANGE) + { + ERR("Unexpected object serial: %s\n", serial_str); + serial_id = info->id; + } + } +#endif + + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; + TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", + form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); + TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id); + + DeviceNode &node = DeviceNode::Add(info->id); + node.mSerial = serial_id; + if(nodeName && *nodeName) node.mName = nodeName; + else node.mName = "PipeWire node #"+std::to_string(info->id); + node.mDevName = devName ? devName : ""; + 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(!node) UNLIKELY 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<MetadataProxy*>(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<std::string> str; + + const char *val{}; + int len{spa_json_next(iter, &val)}; + if(len <= 0) return str; + + str.emplace().resize(static_cast<uint>(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, AudioSourceVirtualClass) == 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_node*>(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<NodeProxy*>(pw_proxy_get_user_data(as<pw_proxy*>(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_metadata*>(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<MetadataProxy*>( + pw_proxy_get_user_data(as<pw_proxy*>(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<const spa_audio_channel> 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 DevFmtX714: map = X714Map; break; + case DevFmtX3D71: map = X71Map; break; + case DevFmtAmbi3D: + info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; + info.channels = device->channelsFromFmt(); + break; + } + if(!map.empty()) + { + info.channels = static_cast<uint32_t>(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<PipeWirePlayback*>(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<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); } + + void outputCallback(); + static void outputCallbackC(void *data) + { static_cast<PipeWirePlayback*>(data)->outputCallback(); } + + void open(const char *name) override; + bool reset() override; + void start() override; + void stop() override; + ClockLatency getClockLatency() override; + + uint64_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<float*[]> 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<spa_io_rate_match*>(area); + break; + } +} + +void PipeWirePlayback::outputCallback() +{ + pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; + if(!pw_buf) UNLIKELY return; + + const al::span<spa_data> datas{pw_buf->buffer->datas, + minu(mNumChannels, pw_buf->buffer->n_datas)}; +#if PW_CHECK_VERSION(0,3,49) + /* In 0.3.49, pw_buffer::requested specifies the number of samples needed + * by the resampler/graph for this audio update. + */ + uint length{static_cast<uint>(pw_buf->requested)}; +#else + /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number + * of samples per update. + */ + uint length{mRateMatch ? mRateMatch->size : 0u}; +#endif + /* If no length is specified, use the device's update size as a fallback. */ + if(!length) UNLIKELY length = mDevice->UpdateSize; + + /* 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). + */ + float **chanptr_end{mChannelPtrs.get()}; + for(const auto &data : datas) + { + length = minu(length, data.maxsize/sizeof(float)); + *chanptr_end = static_cast<float*>(data.data); + ++chanptr_end; + } + + mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length); + + for(const auto &data : datas) + { + data.chunk->offset = 0; + data.chunk->stride = sizeof(float); + data.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<uint> OpenCount{0}; + + uint64_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->mSerial; + 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->mSerial; + 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.mSerial; }; + 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<double>(match->mSampleRate) / mDevice->Frequency}; + const double numbufs{static_cast<double>(mDevice->BufferSize)/mDevice->UpdateSize}; + mDevice->Frequency = match->mSampleRate; + mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5, + 64.0, 8192.0)); + mDevice->BufferSize = static_cast<uint>(numbufs*mDevice->UpdateSize + 0.5); + } + 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<al::byte[]>(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"}; + + /* TODO: Which properties are actually needed here? Any others that could + * be useful? + */ + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname, + PW_KEY_NODE_DESCRIPTION, appname, + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Game", + 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}; + + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, + mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); +#ifdef PW_KEY_TARGET_OBJECT + pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); +#else + pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); +#endif + + MainloopUniqueLock plock{mLoop}; + /* The stream takes overship of 'props', even in the case of failure. */ + 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); + + pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE + | PW_STREAM_FLAG_MAP_BUFFERS}; + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true)) + flags |= PW_STREAM_FLAG_RT_PROCESS; + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream (res: %d)", res}; + + /* Wait for the stream to become paused (ready to start streaming). */ + plock.wait([stream=mStream.get()]() + { + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + return state == PW_STREAM_STATE_PAUSED; + }); + + /* TODO: Update mDevice->UpdateSize with the stream's quantum, and + * mDevice->BufferSize with the total known buffering delay from the head + * of this playback stream to the tail of the device output. + * + * This info is apparently not available until after the stream starts. + */ + plock.unlock(); + + mNumChannels = mDevice->channelsFromFmt(); + mChannelPtrs = std::make_unique<float*[]>(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). + */ + plock.wait([stream=mStream.get()]() + { + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + return state == PW_STREAM_STATE_STREAMING; + }); + + /* HACK: Try to work out the update size and total buffering size. There's + * no actual query for this, so we have to work it out from the stream time + * info, and assume it stays accurate with future updates. The stream time + * info may also not be available right away, so we have to wait until it + * is (up to about 2 seconds). + */ + int wait_count{100}; + do { + pw_time ptime{}; + if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) + { + ERR("Failed to get PipeWire stream time (res: %d)\n", res); + break; + } + + /* The rate match size is the update size for each buffer. */ + const uint updatesize{mRateMatch ? mRateMatch->size : 0u}; +#if PW_CHECK_VERSION(0,3,50) + /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer + * queue size. + */ + if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0) + { + const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers}; + + /* Ensure the delay is in sample frames. */ + const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency * + ptime.rate.num / ptime.rate.denom}; + + mDevice->UpdateSize = updatesize; + mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay + + totalbuffers*updatesize); + break; + } +#else + /* Prior to 0.3.50, we can only measure the delay with the update size, + * assuming one buffer and no resample buffering. + */ + if(ptime.rate.denom > 0 && updatesize > 0) + { + /* Ensure the delay is in sample frames. */ + const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency * + ptime.rate.num / ptime.rate.denom}; + + mDevice->UpdateSize = updatesize; + mDevice->BufferSize = static_cast<uint>(delay + updatesize); + break; + } +#endif + if(!--wait_count) + break; + + plock.unlock(); + std::this_thread::sleep_for(milliseconds{20}); + plock.lock(); + } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING); +} + +void PipeWirePlayback::stop() +{ + 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_n(mStream.get(), &ptime, sizeof(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(ptime.rate.denom < 1) UNLIKELY + { + /* If there's no stream rate, the stream hasn't had a chance to get + * going and return time info yet. Just use dummy values. + */ + 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<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); } + + void inputCallback(); + static void inputCallbackC(void *data) + { static_cast<PipeWireCapture*>(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; + + uint64_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(!pw_buf) UNLIKELY 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<char*>(bufdata->data) + offset, size / mRing->getElemSize()); + + pw_stream_queue_buffer(mStream.get(), pw_buf); +} + + +void PipeWireCapture::open(const char *name) +{ + static std::atomic<uint> OpenCount{0}; + + uint64_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->mSerial; + 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->mSerial; + 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.mSerial; }; + 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<al::byte[]>(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"}; + + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + pw_properties *props{pw_properties_new( + PW_KEY_NODE_NAME, appname, + PW_KEY_NODE_DESCRIPTION, appname, + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Game", + 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}; + + /* 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); +#ifdef PW_KEY_TARGET_OBJECT + pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); +#else + pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); +#endif + + MainloopUniqueLock plock{mLoop}; + mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; + 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, PwIdAny, 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). */ + plock.wait([stream=mStream.get()]() + { + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + 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}; + + plock.wait([stream=mStream.get()]() + { + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + return state == PW_STREAM_STATE_STREAMING; + }); +} + +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<uint>(mRing->readSpace()); } + +void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } + +} // namespace + + +bool PipeWireBackendFactory::init() +{ + if(!pwire_load()) + return false; + + const char *version{pw_get_library_version()}; + if(!check_version(version)) + { + WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version, + pw_get_headers_version()); + return false; + } + TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); + + 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/alc/backends/pipewire.h b/alc/backends/pipewire.h new file mode 100644 index 00000000..5f930239 --- /dev/null +++ b/alc/backends/pipewire.h @@ -0,0 +1,23 @@ +#ifndef BACKENDS_PIPEWIRE_H +#define BACKENDS_PIPEWIRE_H + +#include <string> + +#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/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp index 1e3d0ce8..9c94587d 100644 --- a/alc/backends/portaudio.cpp +++ b/alc/backends/portaudio.cpp @@ -20,16 +20,16 @@ #include "config.h" -#include "backends/portaudio.h" +#include "portaudio.h" #include <cstdio> #include <cstdlib> #include <cstring> -#include "alcmain.h" -#include "alexcpt.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" @@ -38,7 +38,7 @@ namespace { -constexpr ALCchar pa_device[] = "PortAudio Default"; +constexpr char pa_device[] = "PortAudio Default"; #ifdef HAVE_DYNLOAD @@ -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, @@ -85,14 +85,14 @@ struct PortPlayback final : public BackendBase { framesPerBuffer, timeInfo, statusFlags); } - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; - ALuint mUpdateSize{0u}; + uint mUpdateSize{0u}; DEF_NEWDEL(PortPlayback) }; @@ -109,73 +109,79 @@ PortPlayback::~PortPlayback() int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept { - std::lock_guard<PortPlayback> _{*this}; - aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer)); + mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer), + static_cast<uint>(mParams.channelCount)); return 0; } -void PortPlayback::open(const ALCchar *name) +void PortPlayback::open(const char *name) { if(!name) name = pa_device; else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; - - mUpdateSize = mDevice->UpdateSize; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + 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<double>(mDevice->Frequency); - mParams.hostApiSpecificStreamInfo = nullptr; + if(devidopt && *devidopt >= 0) params.device = *devidopt; + else params.device = Pa_GetDefaultOutputDevice(); + params.suggestedLatency = mDevice->BufferSize / static_cast<double>(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{ALC_INVALID_VALUE, "Failed to open stream: %s", + 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; } bool PortPlayback::reset() { const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate); + mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate); mDevice->UpdateSize = mUpdateSize; if(mParams.sampleFormat == paInt8) @@ -194,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; @@ -203,20 +209,17 @@ bool PortPlayback::reset() ERR("Unexpected channel count: %u\n", mParams.channelCount); return false; } - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); return true; } -bool PortPlayback::start() +void PortPlayback::start() { - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); - return false; - } - return true; + const PaError err{Pa_StartStream(mStream)}; + if(err == paNoError) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", + Pa_GetErrorText(err)}; } void PortPlayback::stop() @@ -228,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, @@ -241,11 +244,11 @@ struct PortCapture final : public BackendBase { framesPerBuffer, timeInfo, statusFlags); } - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; PaStream *mStream{nullptr}; PaStreamParameters mParams; @@ -272,18 +275,19 @@ int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long fram } -void PortCapture::open(const ALCchar *name) +void PortCapture::open(const char *name) { if(!name) name = pa_device; else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - ALuint samples{mDevice->BufferSize}; + uint samples{mDevice->BufferSize}; samples = maxu(samples, 100 * mDevice->Frequency / 1000); - ALuint frame_size{mDevice->frameSizeFromFmt()}; + uint frame_size{mDevice->frameSizeFromFmt()}; - mRing = CreateRingBuffer(samples, frame_size, false); + mRing = RingBuffer::Create(samples, frame_size, false); auto devidopt = ConfigValueInt(nullptr, "port", "capture"); if(devidopt && *devidopt >= 0) mParams.device = *devidopt; @@ -310,7 +314,7 @@ void PortCapture::open(const ALCchar *name) break; case DevFmtUInt: case DevFmtUShort: - throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt()); @@ -318,22 +322,19 @@ void PortCapture::open(const ALCchar *name) PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; if(err != paNoError) - throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s", + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", Pa_GetErrorText(err)}; mDevice->DeviceName = name; } -bool PortCapture::start() +void PortCapture::start() { - PaError err{Pa_StartStream(mStream)}; + const PaError err{Pa_StartStream(mStream)}; if(err != paNoError) - { - ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); - return false; - } - return true; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording: %s", Pa_GetErrorText(err)}; } void PortCapture::stop() @@ -344,14 +345,11 @@ void PortCapture::stop() } -ALCuint PortCapture::availableSamples() -{ return static_cast<ALCuint>(mRing->readSpace()); } +uint PortCapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()); } -ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void PortCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } } // namespace @@ -419,19 +417,21 @@ bool PortBackendFactory::init() bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void PortBackendFactory::probe(DevProbe type, std::string *outnames) +std::string PortBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(pa_device, sizeof(pa_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(pa_device, sizeof(pa_device)); + break; } + return outnames; } -BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PortPlayback{device}}; diff --git a/alc/backends/portaudio.h b/alc/backends/portaudio.h index 082e9020..c35ccff2 100644 --- a/alc/backends/portaudio.h +++ b/alc/backends/portaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PORTAUDIO_H #define BACKENDS_PORTAUDIO_H -#include "backends/base.h" +#include "base.h" struct PortBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/pulseaudio.cpp b/alc/backends/pulseaudio.cpp index 4e46460b..4b0e316f 100644 --- a/alc/backends/pulseaudio.cpp +++ b/alc/backends/pulseaudio.cpp @@ -21,41 +21,45 @@ #include "config.h" -#include "backends/pulseaudio.h" - -#include <poll.h> -#include <cstring> +#include "pulseaudio.h" +#include <algorithm> #include <array> -#include <string> -#include <vector> #include <atomic> -#include <thread> -#include <algorithm> -#include <functional> -#include <condition_variable> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "alexcpt.h" -#include "compat.h" +#include <bitset> +#include <chrono> +#include <cstring> +#include <limits> +#include <mutex> +#include <stdint.h> +#include <stdlib.h> +#include <string> +#include <sys/types.h> +#include <utility> + +#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/logging.h" #include "dynload.h" +#include "opthelpers.h" #include "strutils.h" +#include "vector.h" #include <pulse/pulseaudio.h> namespace { +using uint = unsigned int; + #ifdef HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ - MAGIC(pa_mainloop_new); \ - MAGIC(pa_mainloop_free); \ - MAGIC(pa_mainloop_set_poll_func); \ - MAGIC(pa_mainloop_run); \ - MAGIC(pa_mainloop_quit); \ - MAGIC(pa_mainloop_get_api); \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ MAGIC(pa_context_get_state); \ @@ -96,11 +100,21 @@ namespace { MAGIC(pa_stream_disconnect); \ MAGIC(pa_stream_set_buffer_attr_callback); \ MAGIC(pa_stream_begin_write); \ + MAGIC(pa_threaded_mainloop_free); \ + MAGIC(pa_threaded_mainloop_get_api); \ + MAGIC(pa_threaded_mainloop_lock); \ + MAGIC(pa_threaded_mainloop_new); \ + MAGIC(pa_threaded_mainloop_signal); \ + MAGIC(pa_threaded_mainloop_start); \ + MAGIC(pa_threaded_mainloop_stop); \ + MAGIC(pa_threaded_mainloop_unlock); \ + MAGIC(pa_threaded_mainloop_wait); \ MAGIC(pa_channel_map_init_auto); \ MAGIC(pa_channel_map_parse); \ MAGIC(pa_channel_map_snprint); \ MAGIC(pa_channel_map_equal); \ MAGIC(pa_channel_map_superset); \ + MAGIC(pa_channel_position_to_string); \ MAGIC(pa_operation_get_state); \ MAGIC(pa_operation_unref); \ MAGIC(pa_sample_spec_valid); \ @@ -117,12 +131,6 @@ PULSE_FUNCS(MAKE_FUNC) #undef MAKE_FUNC #ifndef IN_IDE_PARSER -#define pa_mainloop_new ppa_mainloop_new -#define pa_mainloop_free ppa_mainloop_free -#define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func -#define pa_mainloop_run ppa_mainloop_run -#define pa_mainloop_quit ppa_mainloop_quit -#define pa_mainloop_get_api ppa_mainloop_get_api #define pa_context_new ppa_context_new #define pa_context_unref ppa_context_unref #define pa_context_get_state ppa_context_get_state @@ -158,12 +166,22 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_stream_get_device_name ppa_stream_get_device_name #define pa_stream_get_latency ppa_stream_get_latency #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback -#define pa_stream_begin_write ppa_stream_begin_write*/ +#define pa_stream_begin_write ppa_stream_begin_write +#define pa_threaded_mainloop_free ppa_threaded_mainloop_free +#define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api +#define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock +#define pa_threaded_mainloop_new ppa_threaded_mainloop_new +#define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal +#define pa_threaded_mainloop_start ppa_threaded_mainloop_start +#define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop +#define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock +#define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait #define pa_channel_map_init_auto ppa_channel_map_init_auto #define pa_channel_map_parse ppa_channel_map_parse #define pa_channel_map_snprint ppa_channel_map_snprint #define pa_channel_map_equal ppa_channel_map_equal #define pa_channel_map_superset ppa_channel_map_superset +#define pa_channel_position_to_string ppa_channel_position_to_string #define pa_operation_get_state ppa_operation_get_state #define pa_operation_unref ppa_operation_unref #define pa_sample_spec_valid ppa_sample_spec_valid @@ -216,211 +234,221 @@ constexpr pa_channel_map MonoChanMap{ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } -}; - -size_t ChannelFromPulse(pa_channel_position_t chan) -{ - switch(chan) - { - case PA_CHANNEL_POSITION_INVALID: break; - case PA_CHANNEL_POSITION_MONO: return FrontCenter; - case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft; - case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight; - case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter; - case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter; - case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft; - case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight; - case PA_CHANNEL_POSITION_LFE: return LFE; - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break; - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break; - case PA_CHANNEL_POSITION_SIDE_LEFT: return SideLeft; - case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight; - case PA_CHANNEL_POSITION_AUX0: return Aux0; - case PA_CHANNEL_POSITION_AUX1: return Aux1; - case PA_CHANNEL_POSITION_AUX2: return Aux2; - case PA_CHANNEL_POSITION_AUX3: return Aux3; - case PA_CHANNEL_POSITION_AUX4: return Aux4; - case PA_CHANNEL_POSITION_AUX5: return Aux5; - case PA_CHANNEL_POSITION_AUX6: return Aux6; - case PA_CHANNEL_POSITION_AUX7: return Aux7; - case PA_CHANNEL_POSITION_AUX8: return Aux8; - case PA_CHANNEL_POSITION_AUX9: return Aux9; - case PA_CHANNEL_POSITION_AUX10: return Aux10; - case PA_CHANNEL_POSITION_AUX11: return Aux11; - case PA_CHANNEL_POSITION_AUX12: return Aux12; - case PA_CHANNEL_POSITION_AUX13: return Aux13; - case PA_CHANNEL_POSITION_AUX14: return Aux14; - case PA_CHANNEL_POSITION_AUX15: return Aux15; - case PA_CHANNEL_POSITION_AUX16: break; - case PA_CHANNEL_POSITION_AUX17: break; - case PA_CHANNEL_POSITION_AUX18: break; - case PA_CHANNEL_POSITION_AUX19: break; - case PA_CHANNEL_POSITION_AUX20: break; - case PA_CHANNEL_POSITION_AUX21: break; - case PA_CHANNEL_POSITION_AUX22: break; - case PA_CHANNEL_POSITION_AUX23: break; - case PA_CHANNEL_POSITION_AUX24: break; - case PA_CHANNEL_POSITION_AUX25: break; - case PA_CHANNEL_POSITION_AUX26: break; - case PA_CHANNEL_POSITION_AUX27: break; - case PA_CHANNEL_POSITION_AUX28: break; - case PA_CHANNEL_POSITION_AUX29: break; - case PA_CHANNEL_POSITION_AUX30: break; - case PA_CHANNEL_POSITION_AUX31: break; - case PA_CHANNEL_POSITION_TOP_CENTER: break; - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft; - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight; - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break; - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft; - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight; - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break; - case PA_CHANNEL_POSITION_MAX: break; +}, X714ChanMap{ + 12, { + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT } - throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan}; -} - -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) -{ - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); - for(ALuint i{0};i < chanmap.channels;++i) - device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = 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) -{ return pa_stream_flags_t(int(lhs) | int(rhs)); } -inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) +constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) +{ return pa_stream_flags_t(lhs | al::to_underlying(rhs)); } +constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { lhs = lhs | rhs; return lhs; } -inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) +constexpr pa_stream_flags_t operator~(pa_stream_flags_t flag) +{ return pa_stream_flags_t(~al::to_underlying(flag)); } +constexpr pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { - lhs = pa_stream_flags_t(int(lhs) & rhs); + lhs = pa_stream_flags_t(al::to_underlying(lhs) & rhs); return lhs; } -inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) +constexpr pa_context_flags_t operator|(pa_context_flags_t lhs, pa_context_flags_t rhs) +{ return pa_context_flags_t(lhs | al::to_underlying(rhs)); } +constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) { - lhs = pa_context_flags_t(int(lhs) | int(rhs)); + lhs = lhs | rhs; return lhs; } -/* Global flags and properties */ -pa_context_flags_t pulse_ctx_flags; +struct DevMap { + std::string name; + std::string device_name; +}; -int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) noexcept +bool checkName(const al::span<const DevMap> list, const std::string &name) { - auto plock = static_cast<std::unique_lock<std::mutex>*>(userdata); - plock->unlock(); - int r{poll(ufds, nfds, timeout)}; - plock->lock(); - return r; + 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<DevMap> PlaybackDevices; +al::vector<DevMap> CaptureDevices; + + +/* Global flags and properties */ +pa_context_flags_t pulse_ctx_flags; + class PulseMainloop { - std::thread mThread; - std::mutex mMutex; - std::condition_variable mCondVar; - pa_mainloop *mMainloop{nullptr}; + pa_threaded_mainloop *mLoop{}; public: - ~PulseMainloop() + PulseMainloop() = default; + PulseMainloop(const PulseMainloop&) = delete; + PulseMainloop(PulseMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } + explicit PulseMainloop(pa_threaded_mainloop *loop) noexcept : mLoop{loop} { } + ~PulseMainloop() { if(mLoop) pa_threaded_mainloop_free(mLoop); } + + PulseMainloop& operator=(const PulseMainloop&) = delete; + PulseMainloop& operator=(PulseMainloop&& rhs) noexcept + { std::swap(mLoop, rhs.mLoop); return *this; } + PulseMainloop& operator=(std::nullptr_t) noexcept { - if(mThread.joinable()) - { - pa_mainloop_quit(mMainloop, 0); - mThread.join(); - } + if(mLoop) + pa_threaded_mainloop_free(mLoop); + mLoop = nullptr; + return *this; } - int mainloop_thread() - { - SetRTPriority(); + explicit operator bool() const noexcept { return mLoop != nullptr; } - std::unique_lock<std::mutex> plock{mMutex}; - mMainloop = pa_mainloop_new(); + auto start() const { return pa_threaded_mainloop_start(mLoop); } + auto stop() const { return pa_threaded_mainloop_stop(mLoop); } - pa_mainloop_set_poll_func(mMainloop, pulse_poll_func, &plock); - mCondVar.notify_all(); + auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } - int ret{}; - pa_mainloop_run(mMainloop, &ret); + auto lock() const { return pa_threaded_mainloop_lock(mLoop); } + auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } - pa_mainloop_free(mMainloop); - mMainloop = nullptr; + auto signal(bool wait=false) const { return pa_threaded_mainloop_signal(mLoop, wait); } - return ret; - } + static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; } - void doLock() { mMutex.lock(); } - void doUnlock() { mMutex.unlock(); } - std::unique_lock<std::mutex> getLock() { return std::unique_lock<std::mutex>{mMutex}; } - std::condition_variable &getCondVar() noexcept { return mCondVar; } - void contextStateCallback(pa_context *context) noexcept + void streamSuccessCallback(pa_stream*, int) noexcept { signal(); } + static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept + { static_cast<PulseMainloop*>(pdata)->streamSuccessCallback(stream, success); } + + void close(pa_context *context, pa_stream *stream=nullptr); + + + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { - pa_context_state_t state{pa_context_get_state(context)}; - if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) - mCondVar.notify_all(); + if(eol) + { + signal(); + return; + } + + /* Skip this device is if it's already in the list. */ + auto match_devname = [info](const DevMap &entry) -> bool + { return entry.device_name == info->name; }; + if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_devname) != PlaybackDevices.cend()) + return; + + /* Make sure the display name (description) is unique. Append a number + * counter as needed. + */ + int count{1}; + std::string newname{info->description}; + while(checkName(PlaybackDevices, newname)) + { + newname = info->description; + newname += " #"; + newname += std::to_string(++count); + } + PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); + DevMap &newentry = PlaybackDevices.back(); + + TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - static void contextStateCallbackC(pa_context *context, void *pdata) noexcept - { static_cast<PulseMainloop*>(pdata)->contextStateCallback(context); } - void streamStateCallback(pa_stream *stream) noexcept + void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept { - pa_stream_state_t state{pa_stream_get_state(stream)}; - if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) - mCondVar.notify_all(); + if(eol) + { + signal(); + return; + } + + /* Skip this device is if it's already in the list. */ + auto match_devname = [info](const DevMap &entry) -> bool + { return entry.device_name == info->name; }; + if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_devname) != CaptureDevices.cend()) + return; + + /* Make sure the display name (description) is unique. Append a number + * counter as needed. + */ + int count{1}; + std::string newname{info->description}; + while(checkName(CaptureDevices, newname)) + { + newname = info->description; + newname += " #"; + newname += std::to_string(++count); + } + CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); + DevMap &newentry = CaptureDevices.back(); + + TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulseMainloop*>(pdata)->streamStateCallback(stream); } - void streamSuccessCallback(pa_stream*, int) noexcept - { mCondVar.notify_all(); } - static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept - { static_cast<PulseMainloop*>(pdata)->streamSuccessCallback(stream, success); } + void probePlaybackDevices(); + void probeCaptureDevices(); + + friend struct MainloopUniqueLock; +}; +struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> { + using std::unique_lock<PulseMainloop>::unique_lock; + MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; + + auto wait() const -> void + { pa_threaded_mainloop_wait(mutex()->mLoop); } - void waitForOperation(pa_operation *op, std::unique_lock<std::mutex> &plock) + template<typename Predicate> + auto wait(Predicate done_waiting) const -> void + { while(!done_waiting()) wait(); } + + void waitForOperation(pa_operation *op) { if(op) { - while(pa_operation_get_state(op) == PA_OPERATION_RUNNING) - mCondVar.wait(plock); + wait([op]{ return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); pa_operation_unref(op); } } - pa_context *connectContext(std::unique_lock<std::mutex> &plock); - pa_stream *connectStream(const char *device_name, std::unique_lock<std::mutex> &plock, - pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, - pa_channel_map *chanmap, BackendType type); + void contextStateCallback(pa_context *context) noexcept + { + pa_context_state_t state{pa_context_get_state(context)}; + if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) + mutex()->signal(); + } - void close(pa_context *context, pa_stream *stream); + void streamStateCallback(pa_stream *stream) noexcept + { + pa_stream_state_t state{pa_stream_get_state(stream)}; + if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) + mutex()->signal(); + } + + pa_context *connectContext(); + pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); }; +using MainloopLockGuard = std::lock_guard<PulseMainloop>; -pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock) +pa_context *MainloopUniqueLock::connectContext() { - const char *name{"OpenAL Soft"}; + pa_context *context{pa_context_new(mutex()->getApi(), nullptr)}; + if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, + "pa_context_new() failed"}; - const PathNamePair &binname = GetProcBinary(); - if(!binname.fname.empty()) - name = binname.fname.c_str(); - - if(!mMainloop) - { - mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_thread), this}; - while(!mMainloop) mCondVar.wait(plock); - } - - pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), name)}; - if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_context_new() failed"}; - - pa_context_set_state_callback(context, &contextStateCallbackC, this); + pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept + { return static_cast<MainloopUniqueLock*>(pdata)->contextStateCallback(ctx); }, this); int err; if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) @@ -435,7 +463,7 @@ pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock) break; } - mCondVar.wait(plock); + wait(); } } pa_context_set_state_callback(context, nullptr, nullptr); @@ -443,24 +471,25 @@ pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock) if(err < 0) { pa_context_unref(context); - throw al::backend_exception{ALC_INVALID_VALUE, "Context did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", pa_strerror(err)}; } return context; } -pa_stream *PulseMainloop::connectStream(const char *device_name, - std::unique_lock<std::mutex> &plock, pa_context *context, pa_stream_flags_t flags, - pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) +pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context *context, + pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, + BackendType type) { const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)}; if(!stream) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_stream_new() failed (%s)", + throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", pa_strerror(pa_context_errno(context))}; - pa_stream_set_state_callback(stream, &streamStateCallbackC, this); + pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept + { return static_cast<MainloopUniqueLock*>(pdata)->streamStateCallback(strm); }, this); int err{(type==BackendType::Playback) ? pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : @@ -468,8 +497,8 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, if(err < 0) { pa_stream_unref(stream); - throw al::backend_exception{ALC_INVALID_VALUE, "%s did not connect (%s)", stream_id, - pa_strerror(err)}; + throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)", + stream_id, pa_strerror(err)}; } pa_stream_state_t state; @@ -479,11 +508,11 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, { err = pa_context_errno(context); pa_stream_unref(stream); - throw al::backend_exception{ALC_INVALID_VALUE, "%s did not get ready (%s)", stream_id, - pa_strerror(err)}; + throw al::backend_exception{al::backend_error::DeviceError, + "%s did not get ready (%s)", stream_id, pa_strerror(err)}; } - mCondVar.wait(plock); + wait(); } pa_stream_set_state_callback(stream, nullptr, nullptr); @@ -492,7 +521,7 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, void PulseMainloop::close(pa_context *context, pa_stream *stream) { - std::lock_guard<std::mutex> _{mMutex}; + MainloopUniqueLock _{*this}; if(stream) { pa_stream_set_state_callback(stream, nullptr, nullptr); @@ -508,88 +537,22 @@ void PulseMainloop::close(pa_context *context, pa_stream *stream) } -/* Used for initial connection test and enumeration. */ -PulseMainloop gGlobalMainloop; - - -struct DevMap { - std::string name; - std::string device_name; -}; - -bool checkName(const al::vector<DevMap> &list, const std::string &name) -{ - 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<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -void device_sink_callback(pa_context*, const pa_sink_info *info, int eol, void *pdata) noexcept -{ - if(eol) - { - static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != PlaybackDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(PlaybackDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = PlaybackDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} - -void probePlaybackDevices(PulseMainloop &mainloop) +void PulseMainloop::probePlaybackDevices() { pa_context *context{}; - pa_stream *stream{}; PlaybackDevices.clear(); try { - auto plock = mainloop.getLock(); + MainloopUniqueLock plock{*this}; + auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); }; - context = mainloop.connectContext(plock); - - constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr, - BackendType::Playback); - pa_operation *op{pa_context_get_sink_info_by_name(context, - pa_stream_get_device_name(stream), device_sink_callback, &mainloop)}; - mainloop.waitForOperation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; + context = plock.connectContext(); + pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)}; + plock.waitForOperation(op); - op = pa_context_get_sink_info_list(context, device_sink_callback, &mainloop); - mainloop.waitForOperation(op, plock); + op = pa_context_get_sink_info_list(context, sink_callback, this); + plock.waitForOperation(op); pa_context_disconnect(context); pa_context_unref(context); @@ -597,74 +560,26 @@ void probePlaybackDevices(PulseMainloop &mainloop) } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) mainloop.close(context, stream); + if(context) close(context); } } - -void device_source_callback(pa_context*, const pa_source_info *info, int eol, void *pdata) noexcept -{ - if(eol) - { - static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != CaptureDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} - -void probeCaptureDevices(PulseMainloop &mainloop) +void PulseMainloop::probeCaptureDevices() { pa_context *context{}; - pa_stream *stream{}; CaptureDevices.clear(); try { - auto plock = mainloop.getLock(); - - context = mainloop.connectContext(plock); - - constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 1; - - stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr, - BackendType::Capture); - pa_operation *op{pa_context_get_source_info_by_name(context, - pa_stream_get_device_name(stream), device_source_callback, &mainloop)}; - mainloop.waitForOperation(op, plock); + MainloopUniqueLock plock{*this}; + auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept + { return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); }; - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; + context = plock.connectContext(); + pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)}; + plock.waitForOperation(op); - op = pa_context_get_source_info_list(context, device_source_callback, &mainloop); - mainloop.waitForOperation(op, plock); + op = pa_context_get_source_info_list(context, src_callback, this); + plock.waitForOperation(op); pa_context_disconnect(context); pa_context_unref(context); @@ -672,58 +587,44 @@ void probeCaptureDevices(PulseMainloop &mainloop) } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) mainloop.close(context, stream); + if(context) close(context); } } +/* Used for initial connection test and enumeration. */ +PulseMainloop gGlobalMainloop; + + struct PulsePlayback final : public BackendBase { - PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } + PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; - static void bufferAttrCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); } - void streamStateCallback(pa_stream *stream) noexcept; - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); } - void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept; - static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); } - void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; - static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); } - void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; - static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); } - void streamMovedCallback(pa_stream *stream) noexcept; - static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); } - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; - void lock() override { mMainloop.doLock(); } - void unlock() override { mMainloop.doUnlock(); } PulseMainloop mMainloop; - std::string mDeviceName; + al::optional<std::string> mDeviceName{al::nullopt}; + bool mIs51Rear{false}; pa_buffer_attr mAttr; pa_sample_spec mSpec; pa_stream *mStream{nullptr}; pa_context *mContext{nullptr}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; DEF_NEWDEL(PulsePlayback) }; @@ -755,19 +656,33 @@ void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Playback stream failure"); + mDevice->handleDisconnect("Playback stream failure"); } - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); } void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept { - void *buf{pa_xmalloc(nbytes)}; - aluMixData(mDevice, buf, static_cast<ALuint>(nbytes/mFrameSize)); + do { + pa_free_cb_t free_func{nullptr}; + auto buflen = static_cast<size_t>(-1); + void *buf{}; + if(pa_stream_begin_write(stream, &buf, &buflen) || !buf) UNLIKELY + { + buflen = nbytes; + buf = pa_xmalloc(buflen); + free_func = pa_xfree; + } + else + buflen = minz(buflen, nbytes); + nbytes -= buflen; + + mDevice->renderSamples(buf, static_cast<uint>(buflen/mFrameSize), mSpec.channels); - int ret{pa_stream_write(stream, buf, nbytes, pa_xfree, 0, PA_SEEK_RELATIVE)}; - if UNLIKELY(ret != PA_OK) - ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); + int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; + if(ret != PA_OK) UNLIKELY + ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); + } while(nbytes > 0); } void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) noexcept @@ -775,20 +690,22 @@ 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<ChannelMap,7> chanmaps{{ - { DevFmtX71, X71ChanMap }, - { DevFmtX61, X61ChanMap }, - { DevFmtX51, X51ChanMap }, - { DevFmtX51Rear, X51RearChanMap }, - { DevFmtQuad, QuadChanMap }, - { DevFmtStereo, StereoChanMap }, - { DevFmtMono, MonoChanMap } + static constexpr std::array<ChannelMap,8> chanmaps{{ + { DevFmtX714, X714ChanMap, false }, + { DevFmtX71, X71ChanMap, false }, + { DevFmtX61, X61ChanMap, false }, + { DevFmtX51, X51ChanMap, false }, + { DevFmtX51, X51RearChanMap, true }, + { DevFmtQuad, QuadChanMap, false }, + { DevFmtStereo, StereoChanMap, false }, + { DevFmtMono, MonoChanMap, false } }}; if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } @@ -798,11 +715,13 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int ); if(chaniter != chanmaps.cend()) { - if(!mDevice->Flags.get<ChannelsRequest>()) + 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); @@ -810,15 +729,15 @@ 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 { if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } mDevice->DeviceName = info->description; @@ -827,37 +746,37 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept { mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); + TRACE("Stream moved to %s\n", mDeviceName->c_str()); } -void PulsePlayback::open(const ALCchar *name) +void PulsePlayback::open(const char *name) { + mMainloop = PulseMainloop::Create(); + mMainloop.start(); + const char *pulse_name{nullptr}; const char *dev_name{nullptr}; - if(name) { if(PlaybackDevices.empty()) - probePlaybackDevices(mMainloop); + mMainloop.probePlaybackDevices(); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; pulse_name = iter->device_name.c_str(); dev_name = iter->name.c_str(); } - auto plock = mMainloop.getLock(); - - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + mContext = plock.connectContext(); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) + if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; @@ -871,18 +790,22 @@ void PulsePlayback::open(const ALCchar *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, + mStream = plock.connectStream(pulse_name, mContext, flags, nullptr, &spec, nullptr, BackendType::Playback); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - mFrameSize = static_cast<ALuint>(pa_frame_size(pa_stream_get_sample_spec(mStream))); + pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }, this); + mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream))); - mDeviceName = pa_stream_get_device_name(mStream); + if(pulse_name) mDeviceName.emplace(pulse_name); + else mDeviceName.reset(); if(!dev_name) { - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); }; + pa_operation *op{pa_context_get_sink_info_by_name(mContext, + pa_stream_get_device_name(mStream), name_callback, this)}; + plock.waitForOperation(op); } else mDevice->DeviceName = dev_name; @@ -890,7 +813,8 @@ void PulsePlayback::open(const ALCchar *name) bool PulsePlayback::reset() { - auto plock = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; + const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; if(mStream) { @@ -903,15 +827,16 @@ bool PulsePlayback::reset() mStream = nullptr; } - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkInfoCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + auto info_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); }; + pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName, info_callback, this)}; + plock.waitForOperation(op); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) + if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0)) + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", false)) { /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some * reason. So if the user wants to adjust the overall device latency, @@ -920,8 +845,8 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) || - !mDevice->Flags.get<FrequencyRequest>()) + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", false) + || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; pa_channel_map chanmap{}; @@ -940,19 +865,20 @@ bool PulsePlayback::reset() chanmap = QuadChanMap; break; case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; + chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap); break; case DevFmtX61: chanmap = X61ChanMap; break; case DevFmtX71: + case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX714: + chanmap = X714ChanMap; + break; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { @@ -981,23 +907,25 @@ bool PulsePlayback::reset() mSpec.rate = mDevice->Frequency; mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample spec"}; + throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; - const ALuint frame_size{static_cast<ALuint>(pa_frame_size(&mSpec))}; + const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec)); mAttr.maxlength = ~0u; mAttr.tlength = mDevice->BufferSize * frame_size; mAttr.prebuf = 0u; mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.fragsize = ~0u; - mStream = mMainloop.connectStream(mDeviceName.c_str(), plock, mContext, flags, &mAttr, &mSpec, - &chanmap, BackendType::Playback); + mStream = plock.connectStream(deviceName, mContext, flags, &mAttr, &mSpec, &chanmap, + BackendType::Playback); - pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); + pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); }, this); + pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }, this); mSpec = *(pa_stream_get_sample_spec(mStream)); - mFrameSize = static_cast<ALuint>(pa_frame_size(&mSpec)); + mFrameSize = static_cast<uint>(pa_frame_size(&mSpec)); if(mDevice->Frequency != mSpec.rate) { @@ -1005,10 +933,10 @@ bool PulsePlayback::reset() * accordingly. */ const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency; - const ALuint perlen{static_cast<ALuint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, - 8192.0))}; - const ALuint buflen{static_cast<ALuint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, - std::numeric_limits<int>::max()/mFrameSize))}; + const auto perlen = static_cast<uint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, + 8192.0)); + const auto buflen = static_cast<uint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, + std::numeric_limits<int>::max()/mFrameSize)); mAttr.maxlength = ~0u; mAttr.tlength = buflen * mFrameSize; @@ -1017,12 +945,14 @@ bool PulsePlayback::reset() op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, &mMainloop); - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); mDevice->Frequency = mSpec.rate; } - pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this); + auto attr_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); }; + pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); bufferAttrCallback(mStream); mDevice->BufferSize = mAttr.tlength / mFrameSize; @@ -1031,25 +961,35 @@ bool PulsePlayback::reset() return true; } -bool PulsePlayback::start() +void PulsePlayback::start() { - auto plock = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; + + /* Write some samples to fill the buffer before we start feeding it newly + * mixed samples. + */ + if(size_t todo{pa_stream_writable_size(mStream)}) + { + void *buf{pa_xmalloc(todo)}; + mDevice->renderSamples(buf, static_cast<uint>(todo/mFrameSize), mSpec.channels); + pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); + } - pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); + pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept + { return static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); }, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); - return true; + plock.waitForOperation(op); } void PulsePlayback::stop() { - auto plock = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); pa_stream_set_write_callback(mStream, nullptr, nullptr); } @@ -1061,23 +1001,23 @@ ClockLatency PulsePlayback::getClockLatency() int neg, err; { - auto _ = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; ret.ClockTime = GetDeviceClockTime(mDevice); err = pa_stream_get_latency(mStream, &latency, &neg); } - if UNLIKELY(err != 0) + if(err != 0) UNLIKELY { - /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon - * after starting the stream and no timing info has been received from - * the server yet. Should we wait, possibly stalling the app, or give a - * dummy value? Either way, it shouldn't be 0. */ + /* If err = -PA_ERR_NODATA, it means we were called too soon after + * starting the stream and no timing info has been received from the + * server yet. Give a generic value since nothing better is available. + */ if(err != -PA_ERR_NODATA) ERR("Failed to get stream latency: 0x%x\n", err); - latency = 0; + latency = mDevice->BufferSize - mDevice->UpdateSize; neg = 0; } - else if UNLIKELY(neg) + else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; @@ -1086,39 +1026,30 @@ 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; - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); } - void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; - static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept - { static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); } - void streamMovedCallback(pa_stream *stream) noexcept; - static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); } - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; ClockLatency getClockLatency() override; - void lock() override { mMainloop.doLock(); } - void unlock() override { mMainloop.doUnlock(); } PulseMainloop mMainloop; - std::string mDeviceName; - - ALCuint mLastReadable{0u}; - al::byte mSilentVal{}; + al::optional<std::string> mDeviceName{al::nullopt}; al::span<const al::byte> mCapBuffer; - ssize_t mCapLen{0}; + size_t mHoleLength{0}; + size_t mPacketLength{0}; + + uint mLastReadable{0u}; + al::byte mSilentVal{}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; @@ -1145,16 +1076,16 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Capture stream failure"); + mDevice->handleDisconnect("Capture stream failure"); } - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); } void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept { if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } mDevice->DeviceName = info->description; @@ -1163,31 +1094,35 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept { mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); + TRACE("Stream moved to %s\n", mDeviceName->c_str()); } -void PulseCapture::open(const ALCchar *name) +void PulseCapture::open(const char *name) { + if(!mMainloop) + { + mMainloop = PulseMainloop::Create(); + mMainloop.start(); + } + const char *pulse_name{nullptr}; if(name) { if(CaptureDevices.empty()) - probeCaptureDevices(mMainloop); + mMainloop.probeCaptureDevices(); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; pulse_name = iter->device_name.c_str(); mDevice->DeviceName = iter->name; } - auto plock = mMainloop.getLock(); - - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + mContext = plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) @@ -1204,20 +1139,21 @@ void PulseCapture::open(const ALCchar *name) case DevFmtX51: chanmap = X51ChanMap; break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; case DevFmtX61: chanmap = X61ChanMap; break; case DevFmtX71: chanmap = X71ChanMap; break; + case DevFmtX714: + chanmap = X714ChanMap; + break; + case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { @@ -1237,16 +1173,16 @@ void PulseCapture::open(const ALCchar *name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mSpec.rate = mDevice->Frequency; mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample format"}; + throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; - const ALuint frame_size{static_cast<ALuint>(pa_frame_size(&mSpec))}; - const ALuint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)}; + const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec)); + const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; @@ -1254,127 +1190,143 @@ void PulseCapture::open(const ALCchar *name) mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) + if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(pulse_name, mContext, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); - pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); + pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); }, this); + pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); }, this); - mDeviceName = pa_stream_get_device_name(mStream); + if(pulse_name) mDeviceName.emplace(pulse_name); + else mDeviceName.reset(); if(mDevice->DeviceName.empty()) { - pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(), - &PulseCapture::sourceNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + { return static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); }; + pa_operation *op{pa_context_get_source_info_by_name(mContext, + pa_stream_get_device_name(mStream), name_callback, this)}; + plock.waitForOperation(op); } } -bool PulseCapture::start() +void PulseCapture::start() { - auto plock = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); - return true; + plock.waitForOperation(op); } void PulseCapture::stop() { - auto plock = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); } -ALCenum PulseCapture::captureSamples(al::byte *buffer, ALCuint samples) +void PulseCapture::captureSamples(al::byte *buffer, uint samples) { al::span<al::byte> dstbuf{buffer, samples * pa_frame_size(&mSpec)}; /* Capture is done in fragment-sized chunks, so we loop until we get all - * that's available */ - mLastReadable -= static_cast<ALCuint>(dstbuf.size()); + * that's available. + */ + mLastReadable -= static_cast<uint>(dstbuf.size()); while(!dstbuf.empty()) { + if(mHoleLength > 0) UNLIKELY + { + const size_t rem{minz(dstbuf.size(), mHoleLength)}; + std::fill_n(dstbuf.begin(), rem, mSilentVal); + dstbuf = dstbuf.subspan(rem); + mHoleLength -= rem; + + continue; + } if(!mCapBuffer.empty()) { const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; - if UNLIKELY(mCapLen < 0) - std::fill_n(dstbuf.begin(), rem, mSilentVal); - else - std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); + std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); dstbuf = dstbuf.subspan(rem); mCapBuffer = mCapBuffer.subspan(rem); continue; } - if UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire)) + if(!mDevice->Connected.load(std::memory_order_acquire)) UNLIKELY break; - auto plock = mMainloop.getLock(); - if(mCapLen != 0) + MainloopUniqueLock plock{mMainloop}; + if(mPacketLength > 0) { pa_stream_drop(mStream); - mCapBuffer = {}; - mCapLen = 0; + mPacketLength = 0; } + const pa_stream_state_t state{pa_stream_get_state(mStream)}; - if UNLIKELY(!PA_STREAM_IS_GOOD(state)) + if(!PA_STREAM_IS_GOOD(state)) UNLIKELY { - aluHandleDisconnect(mDevice, "Bad capture state: %u", state); + mDevice->handleDisconnect("Bad capture state: %u", state); break; } + const void *capbuf; size_t caplen; - if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0) + if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { - aluHandleDisconnect(mDevice, "Failed retrieving capture samples: %s", + mDevice->handleDisconnect("Failed retrieving capture samples: %s", pa_strerror(pa_context_errno(mContext))); break; } plock.unlock(); if(caplen == 0) break; - if UNLIKELY(!capbuf) - mCapLen = -static_cast<ssize_t>(caplen); + if(!capbuf) UNLIKELY + mHoleLength = caplen; else - mCapLen = static_cast<ssize_t>(caplen); - mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen}; + mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen}; + mPacketLength = caplen; } if(!dstbuf.empty()) std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); - - return ALC_NO_ERROR; } -ALCuint PulseCapture::availableSamples() +uint PulseCapture::availableSamples() { - size_t readable{mCapBuffer.size()}; + size_t readable{maxz(mCapBuffer.size(), mHoleLength)}; if(mDevice->Connected.load(std::memory_order_acquire)) { - auto _ = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; size_t got{pa_stream_readable_size(mStream)}; - if UNLIKELY(static_cast<ssize_t>(got) < 0) + if(static_cast<ssize_t>(got) < 0) UNLIKELY { const char *err{pa_strerror(static_cast<int>(got))}; ERR("pa_stream_readable_size() failed: %s\n", err); - aluHandleDisconnect(mDevice, "Failed getting readable size: %s", err); + mDevice->handleDisconnect("Failed getting readable size: %s", err); } else { - const auto caplen = static_cast<size_t>(std::abs(mCapLen)); - if(got > caplen) readable += got - caplen; + /* "readable" is the number of bytes from the last packet that have + * not yet been read by the caller. So add the stream's readable + * size excluding the last packet (the stream size includes the + * last packet until it's dropped). + */ + if(got > mPacketLength) + readable += got - mPacketLength; } } - readable = std::min<size_t>(readable, std::numeric_limits<ALCuint>::max()); - mLastReadable = std::max(mLastReadable, static_cast<ALCuint>(readable)); - return mLastReadable / static_cast<ALCuint>(pa_frame_size(&mSpec)); + /* Avoid uint overflow, and avoid decreasing the readable count. */ + readable = std::min<size_t>(readable, std::numeric_limits<uint>::max()); + mLastReadable = std::max(mLastReadable, static_cast<uint>(readable)); + return mLastReadable / static_cast<uint>(pa_frame_size(&mSpec)); } @@ -1385,18 +1337,18 @@ ClockLatency PulseCapture::getClockLatency() int neg, err; { - auto _ = mMainloop.getLock(); + MainloopUniqueLock plock{mMainloop}; ret.ClockTime = GetDeviceClockTime(mDevice); err = pa_stream_get_latency(mStream, &latency, &neg); } - if UNLIKELY(err != 0) + if(err != 0) UNLIKELY { ERR("Failed to get stream latency: 0x%x\n", err); latency = 0; neg = 0; } - else if UNLIKELY(neg) + else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; @@ -1449,12 +1401,18 @@ bool PulseBackendFactory::init() #endif /* HAVE_DYNLOAD */ pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1)) + if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { - auto plock = gGlobalMainloop.getLock(); - pa_context *context{gGlobalMainloop.connectContext(plock)}; + if(!gGlobalMainloop) + { + gGlobalMainloop = PulseMainloop::Create(); + gGlobalMainloop.start(); + } + + MainloopUniqueLock plock{gGlobalMainloop}; + pa_context *context{plock.connectContext()}; pa_context_disconnect(context); pa_context_unref(context); return true; @@ -1467,31 +1425,35 @@ bool PulseBackendFactory::init() bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void PulseBackendFactory::probe(DevProbe type, std::string *outnames) +std::string PulseBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; switch(type) { - case DevProbe::Playback: - probePlaybackDevices(gGlobalMainloop); + case BackendType::Playback: + gGlobalMainloop.probePlaybackDevices(); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; - case DevProbe::Capture: - probeCaptureDevices(gGlobalMainloop); + case BackendType::Capture: + gGlobalMainloop.probeCaptureDevices(); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } + + return outnames; } -BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PulsePlayback{device}}; diff --git a/alc/backends/pulseaudio.h b/alc/backends/pulseaudio.h index 40f3e305..6690fe8a 100644 --- a/alc/backends/pulseaudio.h +++ b/alc/backends/pulseaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H -#include "backends/base.h" +#include "base.h" class PulseBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/qsa.cpp b/alc/backends/qsa.cpp deleted file mode 100644 index 5ed65798..00000000 --- a/alc/backends/qsa.cpp +++ /dev/null @@ -1,963 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011-2013 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/qsa.h" - -#include <stdlib.h> -#include <stdio.h> -#include <sched.h> -#include <errno.h> -#include <memory.h> -#include <poll.h> - -#include <thread> -#include <memory> -#include <algorithm> - -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" -#include "threads.h" - -#include <sys/asoundlib.h> -#include <sys/neutrino.h> - - -namespace { - -struct qsa_data { - snd_pcm_t* pcmHandle{nullptr}; - int audio_fd{-1}; - - snd_pcm_channel_setup_t csetup{}; - snd_pcm_channel_params_t cparams{}; - - ALvoid* buffer{nullptr}; - ALsizei size{0}; - - std::atomic<ALenum> mKillNow{AL_TRUE}; - std::thread mThread; -}; - -struct DevMap { - ALCchar* name; - int card; - int dev; -}; - -al::vector<DevMap> DeviceNameMap; -al::vector<DevMap> CaptureNameMap; - -constexpr ALCchar qsaDevice[] = "QSA Default"; - -constexpr struct { - int32_t format; -} formatlist[] = { - {SND_PCM_SFMT_FLOAT_LE}, - {SND_PCM_SFMT_S32_LE}, - {SND_PCM_SFMT_U32_LE}, - {SND_PCM_SFMT_S16_LE}, - {SND_PCM_SFMT_U16_LE}, - {SND_PCM_SFMT_S8}, - {SND_PCM_SFMT_U8}, - {0}, -}; - -constexpr struct { - int32_t rate; -} ratelist[] = { - {192000}, - {176400}, - {96000}, - {88200}, - {48000}, - {44100}, - {32000}, - {24000}, - {22050}, - {16000}, - {12000}, - {11025}, - {8000}, - {0}, -}; - -constexpr struct { - int32_t channels; -} channellist[] = { - {8}, - {7}, - {6}, - {4}, - {2}, - {1}, - {0}, -}; - -void deviceList(int type, al::vector<DevMap> *devmap) -{ - snd_ctl_t* handle; - snd_pcm_info_t pcminfo; - int max_cards, card, err, dev; - DevMap entry; - char name[1024]; - snd_ctl_hw_info info; - - max_cards = snd_cards(); - if(max_cards < 0) - return; - - std::for_each(devmap->begin(), devmap->end(), - [](const DevMap &entry) -> void - { free(entry.name); } - ); - devmap->clear(); - - entry.name = strdup(qsaDevice); - entry.card = 0; - entry.dev = 0; - devmap->push_back(entry); - - for(card = 0;card < max_cards;card++) - { - if((err=snd_ctl_open(&handle, card)) < 0) - continue; - - if((err=snd_ctl_hw_info(handle, &info)) < 0) - { - snd_ctl_close(handle); - continue; - } - - for(dev = 0;dev < (int)info.pcmdevs;dev++) - { - if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0) - continue; - - if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) || - (type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE))) - { - snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev); - entry.name = strdup(name); - entry.card = card; - entry.dev = dev; - - devmap->push_back(entry); - TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev); - } - } - snd_ctl_close(handle); - } -} - - -/* Wrappers to use an old-style backend with the new interface. */ -struct PlaybackWrapper final : public BackendBase { - PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~PlaybackWrapper() override; - - void open(const ALCchar *name) override; - bool reset() override; - bool start() override; - void stop() override; - - std::unique_ptr<qsa_data> mExtraData; - - DEF_NEWDEL(PlaybackWrapper) -}; - - -FORCE_ALIGN static int qsa_proc_playback(void *ptr) -{ - PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr); - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - sched_param param; - char* write_ptr; - ALint len; - int sret; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - /* Increase default 10 priority to 11 to avoid jerky sound */ - SchedGet(0, 0, ¶m); - param.sched_priority=param.sched_curpriority+1; - SchedSet(0, 0, SCHED_NOCHANGE, ¶m); - - const ALint frame_size = device->frameSizeFromFmt(); - - std::unique_lock<PlaybackWrapper> dlock{*self}; - while(!data->mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - dlock.unlock(); - sret = poll(&pollitem, 1, 2000); - dlock.lock(); - if(sret == -1) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll error: %s\n", strerror(errno)); - aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - if(sret == 0) - { - ERR("poll timeout\n"); - continue; - } - - len = data->size; - write_ptr = static_cast<char*>(data->buffer); - aluMixData(device, write_ptr, len/frame_size); - while(len>0 && !data->mKillNow.load(std::memory_order_acquire)) - { - int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len); - if(wrote <= 0) - { - if(errno==EAGAIN || errno==EWOULDBLOCK) - continue; - - memset(&status, 0, sizeof(status)); - status.channel = SND_PCM_CHANNEL_PLAYBACK; - - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've underrun the buffer */ - if(status.status == SND_PCM_STATUS_UNDERRUN || - status.status == SND_PCM_STATUS_READY) - { - if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0) - { - aluHandleDisconnect(device, "Playback recovery failed"); - break; - } - } - } - else - { - write_ptr += wrote; - len -= wrote; - } - } - } - - return 0; -} - -/************/ -/* Playback */ -/************/ - -static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int status; - - std::unique_ptr<qsa_data> data{new qsa_data{}}; - data->mKillNow.store(AL_TRUE, std::memory_order_relaxed); - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK); - else - { - if(DeviceNameMap.empty()) - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - - auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == DeviceNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->buffer!=NULL) - { - free(data->buffer); - data->buffer=NULL; - } - - snd_pcm_close(data->pcmHandle); - - self->mExtraData = nullptr; -} - -static ALCboolean qsa_reset_playback(PlaybackWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - int32_t format=-1; - - switch(device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on writes */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS); - - // configure a sound channel - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK; - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.start_mode=SND_PCM_START_FULL; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize; - data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - int original_rate=data->cparams.format.rate; - int original_voices=data->cparams.format.voices; - int original_format=data->cparams.format.format; - int it; - int jt; - - for (it=0; it<1; it++) - { - /* Check for second pass */ - if (it==1) - { - original_rate=ratelist[0].rate; - original_voices=channellist[0].channels; - original_format=formatlist[0].format; - } - - do { - /* At first downgrade sample format */ - jt=0; - do { - if (formatlist[jt].format==data->cparams.format.format) - { - data->cparams.format.format=formatlist[jt+1].format; - break; - } - if (formatlist[jt].format==0) - { - data->cparams.format.format=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.format==0) - { - data->cparams.format.format=original_format; - - /* At secod downgrade sample rate */ - jt=0; - do { - if (ratelist[jt].rate==data->cparams.format.rate) - { - data->cparams.format.rate=ratelist[jt+1].rate; - break; - } - if (ratelist[jt].rate==0) - { - data->cparams.format.rate=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.rate==0) - { - data->cparams.format.rate=original_rate; - data->cparams.format.format=original_format; - - /* At third downgrade channels number */ - jt=0; - do { - if(channellist[jt].channels==data->cparams.format.voices) - { - data->cparams.format.voices=channellist[jt+1].channels; - break; - } - if (channellist[jt].channels==0) - { - data->cparams.format.voices=0; - break; - } - jt++; - } while(1); - } - - if (data->cparams.format.voices==0) - { - break; - } - } - - data->cparams.buf.block.frag_size=device->UpdateSize* - data->cparams.format.voices* - snd_pcm_format_width(data->cparams.format.format)/8; - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - continue; - } - else - { - break; - } - } while(1); - - if (data->cparams.format.voices!=0) - { - break; - } - } - - if (data->cparams.format.voices==0) - { - return ALC_FALSE; - } - } - - if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0) - { - return ALC_FALSE; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK; - if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0) - { - return ALC_FALSE; - } - - /* now fill back to the our AL device */ - device->Frequency=data->cparams.format.rate; - - switch (data->cparams.format.voices) - { - case 1: - device->FmtChans=DevFmtMono; - break; - case 2: - device->FmtChans=DevFmtStereo; - break; - case 4: - device->FmtChans=DevFmtQuad; - break; - case 6: - device->FmtChans=DevFmtX51; - break; - case 7: - device->FmtChans=DevFmtX61; - break; - case 8: - device->FmtChans=DevFmtX71; - break; - default: - device->FmtChans=DevFmtMono; - break; - } - - switch (data->cparams.format.format) - { - case SND_PCM_SFMT_S8: - device->FmtType=DevFmtByte; - break; - case SND_PCM_SFMT_U8: - device->FmtType=DevFmtUByte; - break; - case SND_PCM_SFMT_S16_LE: - device->FmtType=DevFmtShort; - break; - case SND_PCM_SFMT_U16_LE: - device->FmtType=DevFmtUShort; - break; - case SND_PCM_SFMT_S32_LE: - device->FmtType=DevFmtInt; - break; - case SND_PCM_SFMT_U32_LE: - device->FmtType=DevFmtUInt; - break; - case SND_PCM_SFMT_FLOAT_LE: - device->FmtType=DevFmtFloat; - break; - default: - device->FmtType=DevFmtShort; - break; - } - - SetDefaultChannelOrder(device); - - device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt(); - device->NumUpdates=data->csetup.buf.block.frags; - - data->size=data->csetup.buf.block.frag_size; - data->buffer=malloc(data->size); - if (!data->buffer) - { - return ALC_FALSE; - } - - return ALC_TRUE; -} - -static ALCboolean qsa_start_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - try { - data->mKillNow.store(AL_FALSE, std::memory_order_release); - data->mThread = std::thread(qsa_proc_playback, self); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -static void qsa_stop_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable()) - return; - data->mThread.join(); -} - - -PlaybackWrapper::~PlaybackWrapper() -{ - if(mExtraData) - qsa_close_playback(this); -} - -void PlaybackWrapper::open(const ALCchar *name) -{ - if(auto err = qsa_open_playback(this, name)) - throw al::backend_exception{ALC_INVALID_VALUE, "%d", err}; -} - -bool PlaybackWrapper::reset() -{ - if(!qsa_reset_playback(this)) - throw al::backend_exception{ALC_INVALID_VALUE, ""}; - return true; -} - -bool PlaybackWrapper::start() -{ return qsa_start_playback(this); } - -void PlaybackWrapper::stop() -{ qsa_stop_playback(this); } - - -/***********/ -/* Capture */ -/***********/ - -struct CaptureWrapper final : public BackendBase { - CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~CaptureWrapper() override; - - void open(const ALCchar *name) override; - bool start() override; - void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::unique_ptr<qsa_data> mExtraData; - - DEF_NEWDEL(CaptureWrapper) -}; - -static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int format=-1; - int status; - - std::unique_ptr<qsa_data> data{new qsa_data{}}; - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE); - else - { - if(CaptureNameMap.empty()) - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - - auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == CaptureNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - - switch (device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on reads */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - - /* configure a sound channel */ - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.channel=SND_PCM_CHANNEL_CAPTURE; - data->cparams.start_mode=SND_PCM_START_GO; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_VALUE; - } - - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->pcmHandle!=nullptr) - snd_pcm_close(data->pcmHandle); - data->pcmHandle = nullptr; - - self->mExtraData = nullptr; -} - -static void qsa_start_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - int rstatus; - - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - return; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_CAPTURE; - if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0) - { - ERR("capture setup failed: %s\n", snd_strerror(rstatus)); - return; - } - - snd_pcm_capture_go(data->pcmHandle); -} - -static void qsa_stop_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - snd_pcm_capture_flush(data->pcmHandle); -} - -static ALCuint qsa_available_samples(CaptureWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - ALint frame_size = device->frameSizeFromFmt(); - ALint free_size; - int rstatus; - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus)); - return 0; - } - - snd_pcm_capture_go(data->pcmHandle); - return 0; - } - - free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags; - free_size-=status.free; - - return free_size/frame_size; -} - -static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - char* read_ptr; - snd_pcm_channel_status_t status; - int selectret; - int bytes_read; - ALint frame_size=device->frameSizeFromFmt(); - ALint len=samples*frame_size; - int rstatus; - - read_ptr = static_cast<char*>(buffer); - - while (len>0) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - bytes_read=0; - selectret = poll(&pollitem, 1, 2000); - switch (selectret) - { - case -1: - aluHandleDisconnect(device, "Failed to check capture samples"); - return ALC_INVALID_DEVICE; - case 0: - break; - default: - bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len); - break; - } - - if (bytes_read<=0) - { - if ((errno==EAGAIN) || (errno==EWOULDBLOCK)) - { - continue; - } - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've overrun the buffer */ - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", - snd_strerror(rstatus)); - return ALC_INVALID_DEVICE; - } - snd_pcm_capture_go(data->pcmHandle); - } - } - else - { - read_ptr+=bytes_read; - len-=bytes_read; - } - } - - return ALC_NO_ERROR; -} - - -CaptureWrapper::~CaptureWrapper() -{ - if(mExtraData) - qsa_close_capture(this); -} - -void CaptureWrapper::open(const ALCchar *name) -{ - if(auto err = qsa_open_capture(this, name)) - throw al::backend_exception{ALC_INVALID_VALUE, "%d", err}; -} - -bool CaptureWrapper::start() -{ qsa_start_capture(this); return true; } - -void CaptureWrapper::stop() -{ qsa_stop_capture(this); } - -ALCenum CaptureWrapper::captureSamples(al::byte *buffer, ALCuint samples) -{ return qsa_capture_samples(this, buffer, samples); } - -ALCuint CaptureWrapper::availableSamples() -{ return qsa_available_samples(this); } - -} // namespace - - -bool QSABackendFactory::init() -{ return true; } - -bool QSABackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void QSABackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - const char *n = entry.name; - if(n && n[0]) - outnames->append(n, strlen(n)+1); - }; - - switch (type) - { - case DevProbe::Playback: - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device); - break; - case DevProbe::Capture: - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device); - break; - } -} - -BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PlaybackWrapper{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CaptureWrapper{device}}; - return nullptr; -} - -BackendFactory &QSABackendFactory::getFactory() -{ - static QSABackendFactory factory{}; - return factory; -} diff --git a/alc/backends/qsa.h b/alc/backends/qsa.h deleted file mode 100644 index da548bba..00000000 --- a/alc/backends/qsa.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_QSA_H -#define BACKENDS_QSA_H - -#include "backends/base.h" - -struct QSABackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_QSA_H */ diff --git a/alc/backends/sdl2.cpp b/alc/backends/sdl2.cpp index 25b5d4d9..a4a5a9ac 100644 --- a/alc/backends/sdl2.cpp +++ b/alc/backends/sdl2.cpp @@ -20,22 +20,22 @@ #include "config.h" -#include "backends/sdl2.h" +#include "sdl2.h" #include <cassert> #include <cstdlib> #include <cstring> #include <string> -#include "AL/al.h" - -#include "alcmain.h" -#include "alexcpt.h" #include "almalloc.h" -#include "alu.h" -#include "logging.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" -#include <SDL2/SDL.h> +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#include "SDL.h" +_Pragma("GCC diagnostic pop") namespace { @@ -46,30 +46,28 @@ namespace { #define DEVNAME_PREFIX "" #endif -constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; +constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; struct Sdl2Backend final : public BackendBase { - Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { } + Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept { static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); } - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; - void lock() override; - void unlock() override; SDL_AudioDeviceID mDeviceID{0u}; - ALuint mFrameSize{0}; + uint mFrameSize{0}; - ALuint mFrequency{0u}; + uint mFrequency{0u}; DevFmtChannels mFmtChans{}; DevFmtType mFmtType{}; - ALuint mUpdateSize{0u}; + uint mUpdateSize{0u}; DEF_NEWDEL(Sdl2Backend) }; @@ -85,10 +83,10 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept { const auto ulen = static_cast<unsigned int>(len); assert((ulen % mFrameSize) == 0); - aluMixData(mDevice, stream, ulen / mFrameSize); + mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -void Sdl2Backend::open(const ALCchar *name) +void Sdl2Backend::open(const char *name) { SDL_AudioSpec want{}, have{}; @@ -104,59 +102,64 @@ void Sdl2Backend::open(const ALCchar *name) case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast<Uint16>(mDevice->UpdateSize); + want.samples = static_cast<Uint16>(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) - throw al::backend_exception{ALC_INVALID_VALUE, "%s", SDL_GetError()}; - - mDevice->Frequency = static_cast<ALuint>(have.freq); - - if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; - else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; + if(!devid) + throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; + + DevFmtChannels devchans{}; + if(have.channels >= 2) + devchans = DevFmtStereo; + else if(have.channels == 1) + devchans = DevFmtMono; else - throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL channel count: %d", - int{have.channels}}; + { + 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: - throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL format: 0x%04x", + 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<uint>(have.freq); + mFmtChans = devchans; + mFmtType = devtype; + mUpdateSize = have.samples; mDevice->DeviceName = name ? name : defaultDeviceName; } @@ -167,26 +170,17 @@ bool Sdl2Backend::reset() mDevice->FmtChans = mFmtChans; mDevice->FmtType = mFmtType; mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; - SetDefaultWFXChannelOrder(mDevice); + mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ + setDefaultWFXChannelOrder(); return true; } -bool Sdl2Backend::start() -{ - SDL_PauseAudioDevice(mDeviceID, 0); - return true; -} +void Sdl2Backend::start() +{ SDL_PauseAudioDevice(mDeviceID, 0); } void Sdl2Backend::stop() { SDL_PauseAudioDevice(mDeviceID, 1); } -void Sdl2Backend::lock() -{ SDL_LockAudioDevice(mDeviceID); } - -void Sdl2Backend::unlock() -{ SDL_UnlockAudioDevice(mDeviceID); } - } // namespace BackendFactory &SDL2BackendFactory::getFactory() @@ -201,25 +195,28 @@ bool SDL2BackendFactory::init() bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void SDL2BackendFactory::probe(DevProbe type, std::string *outnames) +std::string SDL2BackendFactory::probe(BackendType type) { - if(type != DevProbe::Playback) - return; + std::string outnames; + + if(type != BackendType::Playback) + return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; /* Includes null char. */ - outnames->append(defaultDeviceName, sizeof(defaultDeviceName)); + outnames.append(defaultDeviceName, sizeof(defaultDeviceName)); for(int i{0};i < num_devices;++i) { std::string name{DEVNAME_PREFIX}; name += SDL_GetAudioDeviceName(i, SDL_FALSE); if(!name.empty()) - outnames->append(name.c_str(), name.length()+1); + outnames.append(name.c_str(), name.length()+1); } + return outnames; } -BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new Sdl2Backend{device}}; diff --git a/alc/backends/sdl2.h b/alc/backends/sdl2.h index 041d47ee..3bd8df86 100644 --- a/alc/backends/sdl2.h +++ b/alc/backends/sdl2.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SDL2_H #define BACKENDS_SDL2_H -#include "backends/base.h" +#include "base.h" struct SDL2BackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/sndio.cpp b/alc/backends/sndio.cpp index 7799316f..077e77f2 100644 --- a/alc/backends/sndio.cpp +++ b/alc/backends/sndio.cpp @@ -20,44 +20,52 @@ #include "config.h" -#include "backends/sndio.h" +#include "sndio.h" +#include <functional> +#include <inttypes.h> +#include <poll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> - #include <thread> -#include <functional> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "ringbuffer.h" #include "threads.h" #include "vector.h" -#include "ringbuffer.h" #include <sndio.h> namespace { -static const ALCchar sndio_device[] = "SndIO Default"; +static const char sndio_device[] = "SndIO Default"; + +struct SioPar : public sio_par { + SioPar() { sio_initpar(this); } + void clear() { sio_initpar(this); } +}; struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; sio_hdl *mSndHandle{nullptr}; + uint mFrameStep{}; - al::vector<ALubyte> mBuffer; + al::vector<al::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -74,33 +82,29 @@ SndioPlayback::~SndioPlayback() int SndioPlayback::mixerProc() { + const size_t frameStep{mFrameStep}; + const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; + SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALuint frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { - ALubyte *WritePtr{mBuffer.data()}; - size_t len{mBuffer.size()}; + al::span<al::byte> buffer{mBuffer}; + mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize), + frameStep); + while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { - std::lock_guard<SndioPlayback> _{*this}; - aluMixData(mDevice, WritePtr, static_cast<ALuint>(len/frameSize)); - } - while(len > 0 && !mKillNow.load(std::memory_order_acquire)) - { - size_t wrote{sio_write(mSndHandle, WritePtr, len)}; - if(wrote == 0) + size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; + if(wrote > buffer.size() || wrote == 0) { - ERR("sio_write failed\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); + ERR("sio_write failed: 0x%" PRIx64 "\n", wrote); + mDevice->handleDisconnect("Failed to write playback samples"); break; } - - len -= wrote; - WritePtr += wrote; + buffer = buffer.subspan(wrote); } } @@ -108,126 +112,150 @@ int SndioPlayback::mixerProc() } -void SndioPlayback::open(const ALCchar *name) +void SndioPlayback::open(const char *name) { if(!name) name = sndio_device; else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"}; + 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); - - par.rate = mDevice->Frequency; - par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1); + SioPar par; - switch(mDevice->FmtType) + auto tryfmt = mDevice->FmtType; +retry_params: + switch(tryfmt) { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + case DevFmtFloat: + case DevFmtInt: + par.bits = 32; + par.sig = 1; + break; + case DevFmtUInt: + par.bits = 32; + par.sig = 0; + break; } + par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; + par.msb = 1; + + par.rate = mDevice->Frequency; + par.pchan = mDevice->channelsFromFmt(); par.round = mDevice->UpdateSize; par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return false; + try { + if(!sio_setpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device parameters"}; + + par.clear(); + if(!sio_getpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get device parameters"}; + + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + if(par.pchan < 1) + throw al::backend_exception{al::backend_error::DeviceError, + "No playback channels on device"}; } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return true; + catch(al::backend_exception &e) { + if(tryfmt == DevFmtShort) + throw; + par.clear(); + tryfmt = DevFmtShort; + goto retry_params; } - mDevice->Frequency = par.rate; - mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo); - - if(par.bits == 8 && par.sig == 1) - mDevice->FmtType = DevFmtByte; - else if(par.bits == 8 && par.sig == 0) - mDevice->FmtType = DevFmtUByte; - else if(par.bits == 16 && par.sig == 1) - mDevice->FmtType = DevFmtShort; - else if(par.bits == 16 && par.sig == 0) - mDevice->FmtType = DevFmtUShort; - else if(par.bits == 32 && par.sig == 1) - mDevice->FmtType = DevFmtInt; - else if(par.bits == 32 && par.sig == 0) - mDevice->FmtType = DevFmtUInt; + if(par.bps == 1) + mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte; + else if(par.bps == 2) + mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort; + else if(par.bps == 4) + mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8}; + + mFrameStep = par.pchan; + if(par.pchan != mDevice->channelsFromFmt()) { - ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); - return false; + WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", + DevFmtChannelsString(mDevice->FmtChans)); + if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; + else mDevice->FmtChans = DevFmtStereo; } + mDevice->Frequency = par.rate; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mDevice->UpdateSize = par.round; mDevice->BufferSize = par.bufsz + par.round; - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); + mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps); + if(par.sig == 1) + std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + else if(par.bits == 8) + std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80)); + else if(par.bits == 16) + std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000); + else if(par.bits == 32) + std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u); return true; } -bool SndioPlayback::start() +void SndioPlayback::start() { if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return false; - } + throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + sio_stop(mSndHandle); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - sio_stop(mSndHandle); - return false; } void SndioPlayback::stop() @@ -241,17 +269,22 @@ void SndioPlayback::stop() } +/* TODO: This could be improved by avoiding the ring buffer and record thread, + * counting the available samples with the sio_onmove callback and reading + * directly from the device. However, this depends on reasonable support for + * capture buffer sizes apps may request. + */ struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; @@ -275,150 +308,182 @@ int SndioCapture::recordProc() SetRTPriority(); althrd_setname(RECORD_THREAD_NAME); - const ALuint frameSize{mDevice->frameSizeFromFmt()}; + const uint frameSize{mDevice->frameSizeFromFmt()}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + int nfds_pre{sio_nfds(mSndHandle)}; + if(nfds_pre <= 0) { - auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - if(todo == 0) + mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); + return 1; + } + + auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre)); + + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) + { + /* Wait until there's some samples to read. */ + const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)}; + if(nfds <= 0) { - static char junk[4096]; - sio_read(mSndHandle, junk, - minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); - continue; + mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); + break; + } + int pollres{::poll(fds.get(), static_cast<uint>(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; + mDevice->handleDisconnect("Got POLLHUP from poll events"); + break; + } + if(!(revents&POLLIN)) + continue; - size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; - if(!got) + auto data = mRing->getWriteVector(); + al::span<al::byte> 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; + if(got > buffer.size()) { - aluHandleDisconnect(mDevice, "Failed to read capture samples"); + ERR("sio_read failed: 0x%" PRIx64 "\n", got); + mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got); break; } - data.first.buf += got; - data.first.len -= got; - total += got; + 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; } -void SndioCapture::open(const ALCchar *name) +void SndioCapture::open(const char *name) { if(!name) name = sndio_device; else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mSndHandle = sio_open(nullptr, SIO_REC, 0); + mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"}; - - sio_par par; - sio_initpar(&par); + throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; + SioPar par; switch(mDevice->FmtType) { case DevFmtByte: - par.bps = 1; + par.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{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; + 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{ALC_INVALID_VALUE, "Failed to set device praameters"}; - - if(par.bits != par.bps*8) - throw al::backend_exception{ALC_INVALID_VALUE, + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device praameters"}; + + 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) - throw al::backend_exception{ALC_INVALID_VALUE, + 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 = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false); + mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mDevice->BufferSize = static_cast<uint>(mRing->writeSpace()); + mDevice->UpdateSize = par.round; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mDevice->DeviceName = name; } -bool SndioCapture::start() +void SndioCapture::start() { if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return false; - } + throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; - return true; } catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); + sio_stop(mSndHandle); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start capture thread: %s", e.what()}; } - catch(...) { - } - sio_stop(mSndHandle); - return false; } void SndioCapture::stop() @@ -431,14 +496,11 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -ALCenum SndioCapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void SndioCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint SndioCapture::availableSamples() -{ return static_cast<ALCuint>(mRing->readSpace()); } +uint SndioCapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()); } } // namespace @@ -454,19 +516,21 @@ bool SndIOBackendFactory::init() bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void SndIOBackendFactory::probe(DevProbe type, std::string *outnames) +std::string SndIOBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(sndio_device, sizeof(sndio_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(sndio_device, sizeof(sndio_device)); + break; } + return outnames; } -BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SndioPlayback{device}}; diff --git a/alc/backends/sndio.h b/alc/backends/sndio.h index 1ed63d5e..d9433191 100644 --- a/alc/backends/sndio.h +++ b/alc/backends/sndio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SNDIO_H #define BACKENDS_SNDIO_H -#include "backends/base.h" +#include "base.h" struct SndIOBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/solaris.cpp b/alc/backends/solaris.cpp index 7cc2606e..791609ce 100644 --- a/alc/backends/solaris.cpp +++ b/alc/backends/solaris.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/solaris.h" +#include "solaris.h" #include <sys/ioctl.h> #include <sys/types.h> @@ -34,42 +34,44 @@ #include <errno.h> #include <poll.h> #include <math.h> +#include <string.h> #include <thread> #include <functional> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" -#include "alconfig.h" +#include "albyte.h" +#include "alc/alconfig.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "threads.h" #include "vector.h" -#include "compat.h" #include <sys/audioio.h> namespace { -constexpr ALCchar solaris_device[] = "Solaris Default"; +constexpr char solaris_device[] = "Solaris Default"; 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(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; int mFd{-1}; - al::vector<ALubyte> mBuffer; + uint mFrameStep{}; + al::vector<al::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -89,26 +91,23 @@ int SolarisBackend::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALuint frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_step{mDevice->channelsFromFmt()}; + const uint frame_size{mDevice->frameSizeFromFmt()}; - std::unique_lock<SolarisBackend> dlock{*this}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; - dlock.unlock(); int pret{poll(&pollitem, 1, 1000)}; - dlock.lock(); if(pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno)); break; } else if(pret == 0) @@ -117,9 +116,9 @@ int SolarisBackend::mixerProc() continue; } - ALubyte *write_ptr{mBuffer.data()}; + al::byte *write_ptr{mBuffer.data()}; size_t to_write{mBuffer.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); + mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, write_ptr, to_write)}; @@ -128,12 +127,11 @@ int SolarisBackend::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to write playback samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno)); break; } - to_write -= wrote; + to_write -= static_cast<size_t>(wrote); write_ptr += wrote; } } @@ -142,18 +140,23 @@ int SolarisBackend::mixerProc() } -void SolarisBackend::open(const ALCchar *name) +void SolarisBackend::open(const char *name) { if(!name) name = solaris_device; else if(strcmp(name, solaris_device) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mFd = ::open(solaris_driver.c_str(), O_WRONLY); - if(mFd == -1) - throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", + 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; } @@ -163,36 +166,29 @@ bool SolarisBackend::reset() AUDIO_INITINFO(&info); info.play.sample_rate = mDevice->Frequency; - - if(mDevice->FmtChans != DevFmtMono) - mDevice->FmtChans = DevFmtStereo; - ALuint numChannels{mDevice->channelsFromFmt()}; - info.play.channels = numChannels; - + info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { - case DevFmtByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; - case DevFmtUByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR8; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - info.play.precision = 16; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; + case DevFmtByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + case DevFmtUByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR8; + break; + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + mDevice->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; } - - ALuint 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) { @@ -202,46 +198,54 @@ 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(mDevice); + setDefaultChannelOrder(); - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); + mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); + std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); return true; } -bool SolarisBackend::start() +void SolarisBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return false; } void SolarisBackend::stop() @@ -272,26 +276,26 @@ bool SolarisBackendFactory::init() bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void SolarisBackendFactory::probe(DevProbe type, std::string *outnames) +std::string SolarisBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - { -#ifdef HAVE_STAT - struct stat buf; - if(stat(solaris_driver.c_str(), &buf) == 0) -#endif - outnames->append(solaris_device, sizeof(solaris_device)); - } - break; + case BackendType::Playback: + { + struct stat buf; + if(stat(solaris_driver.c_str(), &buf) == 0) + outnames.append(solaris_device, sizeof(solaris_device)); + } + break; - case DevProbe::Capture: - break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SolarisBackend{device}}; diff --git a/alc/backends/solaris.h b/alc/backends/solaris.h index 98b10593..5da6ad3a 100644 --- a/alc/backends/solaris.h +++ b/alc/backends/solaris.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H -#include "backends/base.h" +#include "base.h" struct SolarisBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp index 37a547af..e834eef4 100644 --- a/alc/backends/wasapi.cpp +++ b/alc/backends/wasapi.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/wasapi.h" +#include "wasapi.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> @@ -43,23 +43,28 @@ #include <ksmedia.h> #endif +#include <algorithm> +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <cstring> #include <deque> +#include <functional> +#include <future> #include <mutex> -#include <atomic> +#include <string> #include <thread> #include <vector> -#include <string> -#include <future> -#include <algorithm> -#include <functional> -#include <condition_variable> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" +#include "albit.h" +#include "alc/alconfig.h" +#include "alnumeric.h" +#include "comptr.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" -#include "compat.h" -#include "converter.h" #include "strutils.h" #include "threads.h" @@ -82,8 +87,15 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x namespace { -inline constexpr REFERENCE_TIME operator "" _reftime(unsigned long long int n) noexcept -{ return static_cast<REFERENCE_TIME>(n); } +using std::chrono::nanoseconds; +using std::chrono::milliseconds; +using std::chrono::seconds; + +using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>; + +inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept +{ return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; } + #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) @@ -92,20 +104,51 @@ inline constexpr REFERENCE_TIME operator "" _reftime(unsigned long long int n) n #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) -#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) +#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) -#define REFTIME_PER_SEC 10000000_reftime +constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept +{ + b |= b>>1; + b |= b>>2; + b |= b>>4; + b |= b>>8; + b |= b>>16; + return b; +} +constexpr DWORD MonoMask{MaskFromTopBits(MONO)}; +constexpr DWORD StereoMask{MaskFromTopBits(STEREO)}; +constexpr DWORD QuadMask{MaskFromTopBits(QUAD)}; +constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)}; +constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)}; +constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; +constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; +constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)}; -#define DEVNAME_HEAD "OpenAL Soft on " +constexpr char DevNameHead[] = "OpenAL Soft on "; +constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; -/* Scales the given value using 64-bit integer math, ceiling the result. */ -inline int64_t ScaleCeil(int64_t val, int64_t new_scale, int64_t old_scale) +/* Scales the given reftime value, rounding the result. */ +inline uint RefTime2Samples(const ReferenceTime &val, uint srate) { - return (val*new_scale + old_scale-1) / old_scale; + const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; + return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max())); } +class GuidPrinter { + char mMsg[64]; + +public: + GuidPrinter(const GUID &guid) + { + std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + } + const char *c_str() const { return mMsg; } +}; + struct PropVariant { PROPVARIANT mProp; @@ -139,10 +182,9 @@ struct DevMap { bool checkName(const al::vector<DevMap> &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<DevMap> PlaybackDevices; @@ -152,15 +194,16 @@ al::vector<DevMap> CaptureDevices; using NameGUIDPair = std::pair<std::string,std::string>; NameGUIDPair get_device_name_and_guid(IMMDevice *device) { - std::string name{DEVNAME_HEAD}; - std::string guid; + static constexpr char UnknownName[]{"Unknown Device Name"}; + static constexpr char UnknownGuid[]{"Unknown Device GUID"}; + std::string name, guid; - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); + ComPtr<IPropertyStore> ps; + HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return { name+"Unknown Device Name", "Unknown Device GUID" }; + return std::make_pair(UnknownName, UnknownGuid); } PropVariant pvprop; @@ -168,14 +211,14 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) if(FAILED(hr)) { WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += "Unknown Device Name"; + name += UnknownName; } else if(pvprop->vt == VT_LPWSTR) name += wstr_to_utf8(pvprop->pwszVal); else { WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += "Unknown Device Name"; + name += UnknownName; } pvprop.clear(); @@ -183,60 +226,61 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) if(FAILED(hr)) { WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = "Unknown Device GUID"; + guid = UnknownGuid; } else if(pvprop->vt == VT_LPWSTR) guid = wstr_to_utf8(pvprop->pwszVal); else { WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = "Unknown Device GUID"; + guid = UnknownGuid; } - ps->Release(); - - return {name, guid}; + return std::make_pair(std::move(name), std::move(guid)); } -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +EndpointFormFactor get_device_formfactor(IMMDevice *device) { - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); + ComPtr<IPropertyStore> ps; + HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; + return UnknownFormFactor; } + EndpointFormFactor formfactor{UnknownFormFactor}; PropVariant pvform; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(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<EndpointFormFactor>(pvform->ulVal); - else if(pvform->vt == VT_EMPTY) - *formfactor = UnknownFormFactor; - else + formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); + else if(pvform->vt != VT_EMPTY) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); - - ps->Release(); + return formfactor; } void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list) { - std::string basename, guidstr; - std::tie(basename, guidstr) = get_device_name_and_guid(device); + for(auto &entry : list) + { + if(entry.devid == devid) + return; + } + + auto name_guid = get_device_name_and_guid(device); int count{1}; - std::string newname{basename}; + std::string newname{name_guid.first}; while(checkName(list, newname)) { - newname = basename; + newname = name_guid.first; newname += " #"; newname += std::to_string(++count); } - list.emplace_back(std::move(newname), std::move(guidstr), devid); + list.emplace_back(std::move(newname), std::move(name_guid.second), devid); const DevMap &newentry = list.back(); TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), @@ -247,7 +291,7 @@ WCHAR *get_device_id(IMMDevice *device) { WCHAR *devid; - HRESULT hr = device->GetId(&devid); + const HRESULT hr{device->GetId(&devid)}; if(FAILED(hr)) { ERR("Failed to get device id: %lx\n", hr); @@ -257,55 +301,47 @@ WCHAR *get_device_id(IMMDevice *device) return devid; } -HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list) +void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list) { - IMMDeviceCollection *coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)}; + al::vector<DevMap>{}.swap(list); + + ComPtr<IMMDeviceCollection> coll; + HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())}; if(FAILED(hr)) { ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return hr; + return; } - IMMDevice *defdev{nullptr}; - WCHAR *defdevid{nullptr}; UINT count{0}; hr = coll->GetCount(&count); if(SUCCEEDED(hr) && count > 0) - { - list.clear(); list.reserve(count); - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, &defdev); - } - if(SUCCEEDED(hr) && defdev != nullptr) + ComPtr<IMMDevice> device; + hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr()); + if(SUCCEEDED(hr)) { - defdevid = get_device_id(defdev); - if(defdevid) - add_device(defdev, defdevid, list); + if(WCHAR *devid{get_device_id(device.get())}) + { + add_device(device.get(), devid, list); + CoTaskMemFree(devid); + } + device = nullptr; } for(UINT i{0};i < count;++i) { - IMMDevice *device; - hr = coll->Item(i, &device); + hr = coll->Item(i, device.getPtr()); if(FAILED(hr)) continue; - WCHAR *devid{get_device_id(device)}; - if(devid) + if(WCHAR *devid{get_device_id(device.get())}) { - if(!defdevid || wcscmp(devid, defdevid) != 0) - add_device(device, devid, list); + add_device(device.get(), devid, list); CoTaskMemFree(devid); } - device->Release(); + device = nullptr; } - - if(defdev) defdev->Release(); - if(defdevid) CoTaskMemFree(defdevid); - coll->Release(); - - return S_OK; } @@ -356,21 +392,6 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) { - class GuidPrinter { - char mMsg[64]; - - public: - GuidPrinter(const GUID &guid) - { - std::snprintf(mMsg, al::size(mMsg), - "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", - DWORD{guid.Data1}, guid.Data2, guid.Data3, - guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], - guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - } - const char *c_str() const { return mMsg; } - }; - const WAVEFORMATEXTENSIBLE *fmtex{ CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; TRACE("%s:\n" @@ -411,9 +432,9 @@ enum class MsgType { CloseDevice, EnumeratePlayback, EnumerateCapture, - QuitThread, - Count + Count, + QuitThread = Count }; constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ @@ -423,8 +444,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ "Stop Device", "Close Device", "Enumerate Playback", - "Enumerate Capture", - "Quit" + "Enumerate Capture" }; @@ -432,7 +452,7 @@ constexpr char MessageStr[static_cast<size_t>(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; @@ -442,18 +462,25 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise<HRESULT> mPromise; + + explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; + static std::thread sThread; static std::deque<Msg> mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; + static std::mutex sThreadLock; + static size_t sInitCount; - std::future<HRESULT> pushMessage(MsgType type) + std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr) { std::promise<HRESULT> promise; std::future<HRESULT> future{promise.get_future()}; - { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); + { + std::lock_guard<std::mutex> _{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -463,84 +490,89 @@ struct WasapiProxy { { std::promise<HRESULT> promise; std::future<HRESULT> future{promise.get_future()}; - { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + { + std::lock_guard<std::mutex> _{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; } - static bool popMessage(Msg &msg) + static Msg popMessage() { std::unique_lock<std::mutex> lock{mMsgQueueLock}; - while(mMsgQueue.empty()) - mMsgQueueCond.wait(lock); - msg = std::move(mMsgQueue.front()); + mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); + Msg msg{std::move(mMsgQueue.front())}; mMsgQueue.pop_front(); - return msg.mType != MsgType::QuitThread; + return msg; } static int messageHandler(std::promise<HRESULT> *promise); + + static HRESULT InitThread() + { + std::lock_guard<std::mutex> _{sThreadLock}; + HRESULT res{S_OK}; + if(!sThread.joinable()) + { + std::promise<HRESULT> promise; + auto future = promise.get_future(); + + sThread = std::thread{&WasapiProxy::messageHandler, &promise}; + res = future.get(); + if(FAILED(res)) + { + sThread.join(); + return res; + } + } + ++sInitCount; + return res; + } + + static void DeinitThread() + { + std::lock_guard<std::mutex> _{sThreadLock}; + if(!--sInitCount && sThread.joinable()) + { + pushMessageStatic(MsgType::QuitThread); + sThread.join(); + } + } }; +std::thread WasapiProxy::sThread; std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue; std::mutex WasapiProxy::mMsgQueueLock; std::condition_variable WasapiProxy::mMsgQueueCond; +std::mutex WasapiProxy::sThreadLock; +size_t WasapiProxy::sInitCount{0}; int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) { TRACE("Starting message thread\n"); - HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(FAILED(cohr)) - { - WARN("Failed to initialize COM: 0x%08lx\n", cohr); - promise->set_value(cohr); - return 0; - } - - void *ptr{}; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + WARN("Failed to initialize COM: 0x%08lx\n", hr); promise->set_value(hr); - CoUninitialize(); return 0; } - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - Enumerator->Release(); - Enumerator = nullptr; - CoUninitialize(); - - TRACE("Message thread initialization complete\n"); promise->set_value(S_OK); promise = nullptr; TRACE("Starting message loop\n"); - ALuint deviceCount{0}; - Msg msg; - while(popMessage(msg)) + while(Msg msg{popMessage()}) { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", - MessageStr[static_cast<size_t>(msg.mType)], static_cast<int>(msg.mType), - decltype(std::declval<void*>()){msg.mProxy}); + TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", + MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType), + static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam)); switch(msg.mType) { case MsgType::OpenDevice: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); + hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); - - if(FAILED(hr)) - { - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - } continue; case MsgType::ResetDevice: @@ -561,78 +593,77 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) case MsgType::CloseDevice: msg.mProxy->closeProxy(); msg.mPromise.set_value(S_OK); - - if(--deviceCount == 0) - CoUninitialize(); continue; case MsgType::EnumeratePlayback: case MsgType::EnumerateCapture: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else { - Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - - if(msg.mType == MsgType::EnumeratePlayback) - hr = probe_devices(Enumerator, eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - hr = probe_devices(Enumerator, eCapture, CaptureDevices); - msg.mPromise.set_value(hr); + void *ptr{}; + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr); + if(FAILED(hr)) + msg.mPromise.set_value(hr); + else + { + ComPtr<IMMDeviceEnumerator> devenum{static_cast<IMMDeviceEnumerator*>(ptr)}; - Enumerator->Release(); - Enumerator = nullptr; + if(msg.mType == MsgType::EnumeratePlayback) + probe_devices(devenum.get(), eRender, PlaybackDevices); + else if(msg.mType == MsgType::EnumerateCapture) + probe_devices(devenum.get(), eCapture, CaptureDevices); + msg.mPromise.set_value(S_OK); + } + continue; } - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - continue; - - default: - ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg.mType)); - msg.mPromise.set_value(E_FAIL); - continue; + case MsgType::QuitThread: + break; } + ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType)); + msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished\n"); + CoUninitialize(); return 0; } 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 ALCchar *name) override; - HRESULT openProxy() override; + void open(const char *name) override; + HRESULT openProxy(const char *name) override; void closeProxy() override; bool reset() override; HRESULT resetProxy() override; - bool start() override; + void start() override; HRESULT startProxy() override; void stop() override; void stopProxy() override; ClockLatency getClockLatency() override; - std::wstring mDevId; - - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioRenderClient *mRender{nullptr}; + HRESULT mOpenStatus{E_FAIL}; + ComPtr<IMMDevice> mMMDev{nullptr}; + ComPtr<IAudioClient> mClient{nullptr}; + ComPtr<IAudioRenderClient> mRender{nullptr}; HANDLE mNotifyEvent{nullptr}; + UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; + std::unique_ptr<char[]> mResampleBuffer{}; + uint mBufferFilled{0}; + SampleConverterPtr mResampler; + + WAVEFORMATEXTENSIBLE mFormat{}; std::atomic<UINT32> mPadding{0u}; + std::mutex mMutex; + std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -641,7 +672,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback::~WasapiPlayback() { - pushMessage(MsgType::CloseDevice).wait(); + if(SUCCEEDED(mOpenStatus)) + { + pushMessage(MsgType::CloseDevice).wait(); + DeinitThread(); + } + mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -651,19 +687,20 @@ WasapiPlayback::~WasapiPlayback() FORCE_ALIGN int WasapiPlayback::mixerProc() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); + mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); return 1; } SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALuint update_size{mDevice->UpdateSize}; - const UINT32 buffer_len{mDevice->BufferSize}; + const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; + const uint update_size{mOrigUpdateSize}; + const UINT32 buffer_len{mOrigBufferSize}; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 written; @@ -671,12 +708,12 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr); break; } mPadding.store(written, std::memory_order_relaxed); - ALuint len{buffer_len - written}; + uint len{buffer_len - written}; if(len < update_size) { DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; @@ -689,16 +726,45 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() hr = mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { - std::unique_lock<WasapiPlayback> dlock{*this}; - aluMixData(mDevice, buffer, len); - mPadding.store(written + len, std::memory_order_relaxed); - dlock.unlock(); + if(mResampler) + { + std::lock_guard<std::mutex> _{mMutex}; + for(UINT32 done{0};done < len;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize, + mFormat.Format.nChannels); + mBufferFilled = mDevice->UpdateSize; + } + + const void *src{mResampleBuffer.get()}; + uint srclen{mBufferFilled}; + uint got{mResampler->convert(&src, &srclen, buffer, len-done)}; + buffer += got*frame_size; + done += got; + + mPadding.store(written + done, std::memory_order_relaxed); + if(srclen) + { + const char *bsrc{static_cast<const char*>(src)}; + std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get()); + } + mBufferFilled = srclen; + } + } + else + { + std::lock_guard<std::mutex> _{mMutex}; + mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); + mPadding.store(written + len, std::memory_order_relaxed); + } hr = mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { ERR("Failed to buffer data: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to send playback samples: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr); break; } } @@ -709,103 +775,101 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } -void WasapiPlayback::open(const ALCchar *name) +void WasapiPlayback::open(const char *name) { - HRESULT hr{S_OK}; + if(SUCCEEDED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, + "Unexpected duplicate open call"}; mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create notify events"}; } - if(SUCCEEDED(hr)) + HRESULT hr{InitThread()}; + if(FAILED(hr)) { - if(name) - { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to init COM thread: 0x%08lx", hr}; + } - 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()) - { - 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(name) + { + if(PlaybackDevices.empty()) + pushMessage(MsgType::EnumeratePlayback); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) + { + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; } } - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr}; + DeinitThread(); + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + mOpenStatus}; } } -HRESULT WasapiPlayback::openProxy() +HRESULT WasapiPlayback::openProxy(const char *name) { - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) + const wchar_t *devid{nullptr}; + if(name) { - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mMMDev); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == PlaybackDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == PlaybackDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + + void *ptr; + ComPtr<IMMDevice> mmdev; + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr)}; if(SUCCEEDED(hr)) { - mClient = static_cast<IAudioClient*>(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); + else + hr = enumerator->GetDevice(devid, mmdev.getPtr()); } - if(FAILED(hr)) { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + mClient = nullptr; + mMMDev = std::move(mmdev); + if(name) mDevice->DeviceName = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + return hr; } void WasapiPlayback::closeProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); mMMDev = nullptr; } @@ -814,24 +878,22 @@ bool WasapiPlayback::reset() { HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; if(FAILED(hr)) - throw al::backend_exception{ALC_INVALID_VALUE, "0x%08lx", hr}; + throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; return true; } HRESULT WasapiPlayback::resetProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; void *ptr; - HRESULT hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = static_cast<IAudioClient*>(ptr); + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; WAVEFORMATEX *wfx; hr = mClient->GetMixFormat(&wfx); @@ -840,6 +902,7 @@ HRESULT WasapiPlayback::resetProxy() ERR("Failed to get mix format: 0x%08lx\n", hr); return hr; } + TraceFormat("Device mix format", wfx); WAVEFORMATEXTENSIBLE OutputType; if(!MakeExtensible(&OutputType, wfx)) @@ -850,97 +913,116 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - const REFERENCE_TIME per_time{mDevice->UpdateSize * REFTIME_PER_SEC / mDevice->Frequency}; - const REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; + const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + bool isRear51{false}; - if(!mDevice->Flags.get<FrequencyRequest>()) + if(!mDevice->Flags.test(FrequencyRequest)) mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!mDevice->Flags.get<ChannelsRequest>()) + if(!mDevice->Flags.test(ChannelsRequest)) { - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) + /* If not requesting a channel configuration, auto-select given what + * fits the mask's lsb (to ensure no gaps in the output channels). If + * there's no mask, we can only assume mono or stereo. + */ + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) mDevice->FmtChans = DevFmtX71; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) + { + mDevice->FmtChans = DevFmtX51; + isRear51 = true; + } + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; else - ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); + } + else + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); } OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtAmbi3D: + mDevice->FmtChans = DevFmtStereo; + /*fall-through*/ + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + case DevFmtX3D71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + case DevFmtX714: + OutputType.Format.nChannels = 12; + OutputType.dwChannelMask = X7DOT1DOT4; + break; } switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.Samples.wValidBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + /* fall-through */ + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; } OutputType.Format.nSamplesPerSec = mDevice->Frequency; @@ -953,7 +1035,7 @@ HRESULT WasapiPlayback::resetProxy() hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); if(FAILED(hr)) { - ERR("Failed to check format support: 0x%08lx\n", hr); + WARN("Failed to check format support: 0x%08lx\n", hr); hr = mClient->GetMixFormat(&wfx); } if(FAILED(hr)) @@ -973,27 +1055,76 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) - mDevice->FmtChans = DevFmtX71; + if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; else + mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); + + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + /* Don't update the channel format if the requested format fits what's + * 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; + /* When requesting a channel configuration, make sure it fits the + * mask's lsb (to ensure no gaps in the output channels). If + * there's no mask, assume the request fits with enough channels. + */ + switch(mDevice->FmtChans) + { + case DevFmtMono: + chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); + break; + case DevFmtStereo: + chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); + break; + case DevFmtQuad: + chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); + break; + case DevFmtX51: + chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); + break; + case DevFmtX61: + chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); + break; + case DevFmtX71: + case DevFmtX3D71: + chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); + break; + case DevFmtX714: + chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); + case DevFmtAmbi3D: + break; + } + } + if(!chansok) + { + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; + else + { + ERR("Unhandled 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)) @@ -1017,7 +1148,7 @@ HRESULT WasapiPlayback::resetProxy() } else { - ERR("Unhandled format sub-type\n"); + ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); mDevice->FmtType = DevFmtShort; if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; @@ -1026,25 +1157,24 @@ HRESULT WasapiPlayback::resetProxy() } OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; } + mFormat = OutputType; - EndpointFormFactor formfactor = UnknownFormFactor; - get_device_formfactor(mMMDev, &formfactor); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - (formfactor == Headphones || formfactor == Headset)); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; + mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); + hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { ERR("Failed to initialize audio client: 0x%08lx\n", hr); return hr; } - UINT32 buffer_len{}, min_len{}; - REFERENCE_TIME min_per{}; - hr = mClient->GetDevicePeriod(&min_per, nullptr); + UINT32 buffer_len{}; + ReferenceTime min_per{}; + hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr); if(SUCCEEDED(hr)) hr = mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) @@ -1056,11 +1186,31 @@ HRESULT WasapiPlayback::resetProxy() /* Find the nearest multiple of the period size to the update size */ if(min_per < per_time) min_per *= maxi64((per_time + min_per/2) / min_per, 1); - min_len = static_cast<UINT32>(ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC)); - min_len = minu(min_len, buffer_len/2); - mDevice->UpdateSize = min_len; - mDevice->BufferSize = buffer_len; + mOrigBufferSize = buffer_len; + mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2); + + mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency / + mFormat.Format.nSamplesPerSec); + mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), + mDevice->BufferSize/2); + + mResampler = nullptr; + mResampleBuffer = nullptr; + mBufferFilled = 0; + if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + { + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, + mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, + Resampler::FastBSinc24); + mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} * + mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); + + TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, + mDevice->UpdateSize); + } hr = mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) @@ -1073,17 +1223,19 @@ HRESULT WasapiPlayback::resetProxy() } -bool WasapiPlayback::start() +void WasapiPlayback::start() { - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? true : false; + const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start playback: 0x%lx", hr}; } HRESULT WasapiPlayback::startProxy() { ResetEvent(mNotifyEvent); - HRESULT hr = mClient->Start(); + HRESULT hr{mClient->Start()}; if(FAILED(hr)) { ERR("Failed to start audio client: 0x%08lx\n", hr); @@ -1094,13 +1246,12 @@ HRESULT WasapiPlayback::startProxy() hr = mClient->GetService(IID_IAudioRenderClient, &ptr); if(SUCCEEDED(hr)) { - mRender = static_cast<IAudioRenderClient*>(ptr); + mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; } catch(...) { - mRender->Release(); mRender = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; @@ -1125,7 +1276,6 @@ void WasapiPlayback::stopProxy() mKillNow.store(true, std::memory_order_release); mThread.join(); - mRender->Release(); mRender = nullptr; mClient->Stop(); } @@ -1135,39 +1285,44 @@ ClockLatency WasapiPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard<WasapiPlayback> _{*this}; + std::lock_guard<std::mutex> _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; - ret.Latency /= mDevice->Frequency; + ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; + ret.Latency /= mFormat.Format.nSamplesPerSec; + if(mResampler) + { + auto extra = mResampler->currentInputDelay(); + ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency; + ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency; + } return ret; } 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 ALCchar *name) override; - HRESULT openProxy() override; + void open(const char *name) override; + HRESULT openProxy(const char *name) override; void closeProxy() override; HRESULT resetProxy() override; - bool start() override; + void start() override; HRESULT startProxy() override; void stop() override; void stopProxy() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::wstring mDevId; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioCaptureClient *mCapture{nullptr}; + HRESULT mOpenStatus{E_FAIL}; + ComPtr<IMMDevice> mMMDev{nullptr}; + ComPtr<IAudioClient> mClient{nullptr}; + ComPtr<IAudioCaptureClient> mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; ChannelConverter mChannelConv{}; @@ -1182,7 +1337,12 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { - pushMessage(MsgType::CloseDevice).wait(); + if(SUCCEEDED(mOpenStatus)) + { + pushMessage(MsgType::CloseDevice).wait(); + DeinitThread(); + } + mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -1192,11 +1352,11 @@ WasapiCapture::~WasapiCapture() FORCE_ALIGN int WasapiCapture::recordProc() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); + mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); return 1; } @@ -1232,11 +1392,11 @@ FORCE_ALIGN int WasapiCapture::recordProc() size_t dstframes; if(mSampleConv) { - const ALvoid *srcdata{rdata}; - ALuint srcframes{numsamples}; + const void *srcdata{rdata}; + uint srcframes{numsamples}; dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast<ALuint>(minz(data.first.len, INT_MAX))); + static_cast<uint>(minz(data.first.len, INT_MAX))); if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) { /* If some source samples remain, all of the first dest @@ -1244,12 +1404,12 @@ FORCE_ALIGN int WasapiCapture::recordProc() * dest block, do another run for the second block. */ dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast<ALuint>(minz(data.second.len, INT_MAX))); + static_cast<uint>(minz(data.second.len, INT_MAX))); } } else { - const auto framesize = static_cast<ALuint>(mDevice->frameSizeFromFmt()); + const uint framesize{mDevice->frameSizeFromFmt()}; size_t len1{minz(data.first.len, numsamples)}; size_t len2{minz(data.second.len, numsamples-len1)}; @@ -1268,7 +1428,7 @@ FORCE_ALIGN int WasapiCapture::recordProc() if(FAILED(hr)) { - aluHandleDisconnect(mDevice, "Failed to capture samples: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr); break; } @@ -1282,119 +1442,112 @@ FORCE_ALIGN int WasapiCapture::recordProc() } -void WasapiCapture::open(const ALCchar *name) +void WasapiCapture::open(const char *name) { - HRESULT hr{S_OK}; + if(SUCCEEDED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, + "Unexpected duplicate open call"}; mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { - ERR("Failed to create notify event: %lu\n", GetLastError()); - hr = E_FAIL; + ERR("Failed to create notify events: %lu\n", GetLastError()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create notify events"}; } - if(SUCCEEDED(hr)) + HRESULT hr{InitThread()}; + if(FAILED(hr)) { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to init COM thread: 0x%08lx", hr}; + } - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == CaptureDevices.cend()) - { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == CaptureDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } + if(name) + { + if(CaptureDevices.empty()) + pushMessage(MsgType::EnumerateCapture); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) + { + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; } } - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr}; + DeinitThread(); + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + mOpenStatus}; } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "Out of memory"}; - throw al::backend_exception{ALC_INVALID_VALUE, "Device reset failed"}; + throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; + throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"}; } } -HRESULT WasapiCapture::openProxy() +HRESULT WasapiCapture::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == CaptureDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == CaptureDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; if(SUCCEEDED(hr)) { - auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &mMMDev); + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); + hr = enumerator->GetDevice(devid, mMMDev.getPtr()); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) - { - mClient = static_cast<IAudioClient*>(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; - } - if(FAILED(hr)) { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + mClient = nullptr; + if(name) mDevice->DeviceName = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + return hr; } void WasapiCapture::closeProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); mMMDev = nullptr; } HRESULT WasapiCapture::resetProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; void *ptr; @@ -1404,86 +1557,112 @@ HRESULT WasapiCapture::resetProxy() ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = static_cast<IAudioClient*>(ptr); + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; + + WAVEFORMATEX *wfx; + hr = mClient->GetMixFormat(&wfx); + if(FAILED(hr)) + { + ERR("Failed to get capture format: 0x%08lx\n", hr); + return hr; + } + TraceFormat("Device capture format", wfx); + + WAVEFORMATEXTENSIBLE InputType{}; + if(!MakeExtensible(&InputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = nullptr; + + const bool isRear51{InputType.Format.nChannels == 6 + && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR}; // Make sure buffer is at least 100ms in size - REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; - buf_time = maxu64(buf_time, REFTIME_PER_SEC/10); + ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); - WAVEFORMATEXTENSIBLE OutputType{}; - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + InputType = {}; + InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; + case DevFmtMono: + InputType.Format.nChannels = 1; + InputType.dwChannelMask = MONO; + break; + case DevFmtStereo: + InputType.Format.nChannels = 2; + InputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + InputType.Format.nChannels = 4; + InputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + InputType.Format.nChannels = 6; + InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; + break; + case DevFmtX61: + InputType.Format.nChannels = 7; + InputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + InputType.Format.nChannels = 8; + InputType.dwChannelMask = X7DOT1; + break; + case DevFmtX714: + InputType.Format.nChannels = 12; + InputType.dwChannelMask = X7DOT1DOT4; + break; - case DevFmtAmbi3D: - return E_FAIL; + case DevFmtX3D71: + case DevFmtAmbi3D: + return E_FAIL; } switch(mDevice->FmtType) { - /* NOTE: Signedness doesn't matter, the converter will handle it. */ - case DevFmtByte: - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtShort: - case DevFmtUShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtInt: - case DevFmtUInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; + /* NOTE: Signedness doesn't matter, the converter will handle it. */ + case DevFmtByte: + case DevFmtUByte: + InputType.Format.wBitsPerSample = 8; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtShort: + case DevFmtUShort: + InputType.Format.wBitsPerSample = 16; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtInt: + case DevFmtUInt: + InputType.Format.wBitsPerSample = 32; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + InputType.Format.wBitsPerSample = 32; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; + InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; + InputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format); + InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels * + InputType.Format.wBitsPerSample / 8); + InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * + InputType.Format.nBlockAlign; + InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); - TraceFormat("Requesting capture format", &OutputType.Format); - WAVEFORMATEX *wfx; - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + TraceFormat("Requesting capture format", &InputType.Format); + hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); + if(FAILED(hr)) + { + WARN("Failed to check capture format support: 0x%08lx\n", hr); + hr = mClient->GetMixFormat(&wfx); + } if(FAILED(hr)) { - ERR("Failed to check format support: 0x%08lx\n", hr); + ERR("Failed to find a supported capture format: 0x%08lx\n", hr); return hr; } @@ -1493,92 +1672,132 @@ HRESULT WasapiCapture::resetProxy() if(wfx != nullptr) { TraceFormat("Got capture format", wfx); - if(!(wfx->nChannels == OutputType.Format.nChannels || - (wfx->nChannels == 1 && OutputType.Format.nChannels == 2) || - (wfx->nChannels == 2 && OutputType.Format.nChannels == 1))) + if(!MakeExtensible(&InputType, wfx)) { - ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample, - wfx->nSamplesPerSec); CoTaskMemFree(wfx); return E_FAIL; } + CoTaskMemFree(wfx); + wfx = nullptr; - if(!MakeExtensible(&OutputType, wfx)) + auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept + -> bool { - CoTaskMemFree(wfx); + switch(device->FmtChans) + { + /* If the device wants mono, we can handle any input. */ + case DevFmtMono: + return true; + /* If the device wants stereo, we can handle mono or stereo input. */ + case DevFmtStereo: + return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO)) + || (chancount == 1 && (chanmask&MonoMask) == MONO); + /* Otherwise, the device must match the input type. */ + case DevFmtQuad: + return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD)); + /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */ + case DevFmtX51: + return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)); + case DevFmtX61: + return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1)); + case DevFmtX71: + case DevFmtX3D71: + return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); + case DevFmtX714: + return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4)); + case DevFmtAmbi3D: + return (chanmask == 0 && chancount == device->channelsFromFmt()); + } + return false; + }; + if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask)) + { + ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels, + (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample, + InputType.Format.nSamplesPerSec); return E_FAIL; } - CoTaskMemFree(wfx); - wfx = nullptr; } - DevFmtType srcType; - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) + DevFmtType srcType{}; + if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { - if(OutputType.Format.wBitsPerSample == 8) + if(InputType.Format.wBitsPerSample == 8) srcType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) + else if(InputType.Format.wBitsPerSample == 16) srcType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) + else if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtInt; else { - ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample); + ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample); return E_FAIL; } } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - if(OutputType.Format.wBitsPerSample == 32) + if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtFloat; else { - ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample); + ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample); return E_FAIL; } } else { - ERR("Unhandled format sub-type\n"); + ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str()); return E_FAIL; } - if(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2) + if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1) { - mChannelConv = ChannelConverter{srcType, DevFmtStereo, mDevice->FmtChans}; - TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType)); + uint chanmask{(1u<<InputType.Format.nChannels) - 1u}; + /* Exclude LFE from the downmix. */ + if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY)) + { + constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY); + const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1}; + chanmask &= ~(1u << lfeidx); + } + + mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask, + mDevice->FmtChans}; + TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType)); /* The channel converter always outputs float, so change the input type * for the resampler/type-converter. */ srcType = DevFmtFloat; } - else if(mDevice->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1) + else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1) { - mChannelConv = ChannelConverter{srcType, DevFmtMono, mDevice->FmtChans}; + mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans}; TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); srcType = DevFmtFloat; } - if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) + if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { - mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), - OutputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24); + mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType, + mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency, + Resampler::FastBSinc24); if(!mSampleConv) { ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); + mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); return E_FAIL; } TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); } - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); + hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time.count(), 0, &InputType.Format, nullptr); if(FAILED(hr)) { ERR("Failed to initialize audio client: 0x%08lx\n", hr); @@ -1586,8 +1805,8 @@ HRESULT WasapiCapture::resetProxy() } UINT32 buffer_len{}; - REFERENCE_TIME min_per{}; - hr = mClient->GetDevicePeriod(&min_per, nullptr); + ReferenceTime min_per{}; + hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr); if(SUCCEEDED(hr)) hr = mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) @@ -1595,12 +1814,10 @@ HRESULT WasapiCapture::resetProxy() ERR("Failed to get buffer size: 0x%08lx\n", hr); return hr; } - mDevice->UpdateSize = static_cast<ALuint>(ScaleCeil(min_per, mDevice->Frequency, - REFTIME_PER_SEC)); + mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency); mDevice->BufferSize = buffer_len; - buffer_len = maxu(mDevice->BufferSize, buffer_len); - mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false); + mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false); hr = mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) @@ -1613,10 +1830,12 @@ HRESULT WasapiCapture::resetProxy() } -bool WasapiCapture::start() +void WasapiCapture::start() { - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? true : false; + const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording: 0x%lx", hr}; } HRESULT WasapiCapture::startProxy() @@ -1634,13 +1853,12 @@ HRESULT WasapiCapture::startProxy() hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); if(SUCCEEDED(hr)) { - mCapture = static_cast<IAudioCaptureClient*>(ptr); + mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; } catch(...) { - mCapture->Release(); mCapture = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; @@ -1668,21 +1886,17 @@ void WasapiCapture::stopProxy() mKillNow.store(true, std::memory_order_release); mThread.join(); - mCapture->Release(); mCapture = nullptr; mClient->Stop(); mClient->Reset(); } -ALCuint WasapiCapture::availableSamples() -{ return static_cast<ALCuint>(mRing->readSpace()); } +void WasapiCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCenum WasapiCapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +uint WasapiCapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()); } } // namespace @@ -1693,48 +1907,78 @@ bool WasapiBackendFactory::init() if(FAILED(InitResult)) try { - std::promise<HRESULT> promise; - auto future = promise.get_future(); + auto res = std::async(std::launch::async, []() -> HRESULT + { + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; + if(FAILED(hr)) + { + WARN("Failed to initialize COM: 0x%08lx\n", hr); + return hr; + } + + void *ptr{}; + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr); + if(FAILED(hr)) + { + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + CoUninitialize(); + return hr; + } + static_cast<IMMDeviceEnumerator*>(ptr)->Release(); + CoUninitialize(); + + return S_OK; + }); - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); - InitResult = future.get(); + InitResult = res.get(); } catch(...) { } - return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; + return SUCCEEDED(InitResult); } bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void WasapiBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WasapiBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + struct ProxyControl { + HRESULT mResult{}; + ProxyControl() { mResult = WasapiProxy::InitThread(); } + ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); } }; - HRESULT hr{}; + ProxyControl proxy; + + std::string outnames; + if(FAILED(proxy.mResult)) + return outnames; + switch(type) { - case DevProbe::Playback: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get(); - if(SUCCEEDED(hr)) - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + case BackendType::Playback: + WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait(); + for(const DevMap &entry : PlaybackDevices) + { + /* +1 to also append the null char (to ensure a null-separated list + * and double-null terminated list). + */ + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + } break; - case DevProbe::Capture: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get(); - if(SUCCEEDED(hr)) - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + case BackendType::Capture: + WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait(); + for(const DevMap &entry : CaptureDevices) + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); break; } + + return outnames; } -BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WasapiPlayback{device}}; diff --git a/alc/backends/wasapi.h b/alc/backends/wasapi.h index 067dd259..bb2671ee 100644 --- a/alc/backends/wasapi.h +++ b/alc/backends/wasapi.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WASAPI_H #define BACKENDS_WASAPI_H -#include "backends/base.h" +#include "base.h" struct WasapiBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/wave.cpp b/alc/backends/wave.cpp index 7bcc3436..1b40640c 100644 --- a/alc/backends/wave.cpp +++ b/alc/backends/wave.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/wave.h" +#include "wave.h" #include <algorithm> #include <atomic> @@ -33,18 +33,15 @@ #include <functional> #include <thread> -#include "AL/al.h" - +#include "albit.h" #include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" -#include "alexcpt.h" +#include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "alu.h" -#include "compat.h" -#include "endiantest.h" -#include "logging.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "opthelpers.h" #include "strutils.h" #include "threads.h" #include "vector.h" @@ -56,50 +53,53 @@ using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; -constexpr ALCchar waveDevice[] = "Wave File Writer"; +using ubyte = unsigned char; +using ushort = unsigned short; + +constexpr char waveDevice[] = "Wave File Writer"; -constexpr ALubyte SUBTYPE_PCM[]{ +constexpr ubyte SUBTYPE_PCM[]{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; -constexpr ALubyte SUBTYPE_FLOAT[]{ +constexpr ubyte SUBTYPE_FLOAT[]{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; -constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{ +constexpr ubyte SUBTYPE_BFORMAT_PCM[]{ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }; -constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{ +constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }; -void fwrite16le(ALushort val, FILE *f) +void fwrite16le(ushort val, FILE *f) { - ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) }; + ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) }; fwrite(data, 1, 2, f); } -void fwrite32le(ALuint val, FILE *f) +void fwrite32le(uint val, FILE *f) { - ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff), - static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) }; + ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff), + static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) }; fwrite(data, 1, 4, f); } struct WaveBackend final : public BackendBase { - WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { } + WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; FILE *mFile{nullptr}; @@ -126,12 +126,13 @@ int WaveBackend::mixerProc() althrd_setname(MIXER_THREAD_NAME); - const ALuint frameSize{mDevice->frameSizeFromFmt()}; + const size_t frameStep{mDevice->channelsFromFmt()}; + const size_t frameSize{mDevice->frameSizeFromFmt()}; int64_t done{0}; auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); @@ -145,45 +146,35 @@ int WaveBackend::mixerProc() } while(avail-done >= mDevice->UpdateSize) { - { - std::lock_guard<WaveBackend> _{*this}; - aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize); - } + mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); done += mDevice->UpdateSize; - if(!IS_LITTLE_ENDIAN) + if(al::endian::native != al::endian::little) { - const ALuint bytesize{mDevice->bytesFromFmt()}; + const uint bytesize{mDevice->bytesFromFmt()}; if(bytesize == 2) { - ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data()); - const size_t len{mBuffer.size() / 2}; - for(size_t i{0};i < len;i++) - { - const ALushort samp{samples[i]}; - samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8)); - } + const size_t len{mBuffer.size() & ~size_t{1}}; + for(size_t i{0};i < len;i+=2) + std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - ALuint *samples = reinterpret_cast<ALuint*>(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 ALuint samp{samples[i]}; - samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) | - ((samp<<8)&0x00ff0000) | (samp<<24); + std::swap(mBuffer[i ], mBuffer[i+3]); + std::swap(mBuffer[i+1], mBuffer[i+2]); } } } - size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - (void)fs; - if(ferror(mFile)) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; + if(fs < mDevice->UpdateSize || ferror(mFile)) { ERR("Error writing to file\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); + mDevice->handleDisconnect("Failed to write playback samples"); break; } } @@ -196,49 +187,54 @@ int WaveBackend::mixerProc() if(done >= mDevice->Frequency) { seconds s{done/mDevice->Frequency}; + done %= mDevice->Frequency; start += s; - done -= mDevice->Frequency*s.count(); } } return 0; } -void WaveBackend::open(const ALCchar *name) +void WaveBackend::open(const char *name) { - const char *fname{GetConfigValue(nullptr, "wave", "file", "")}; - if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"}; + auto fname = ConfigValueStr(nullptr, "wave", "file"); + if(!fname) throw al::backend_exception{al::backend_error::NoDevice, + "No wave output filename"}; if(!name) name = waveDevice; else if(strcmp(name, waveDevice) != 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* 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{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname, - strerror(errno)}; + throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", + fname->c_str(), strerror(errno)}; mDevice->DeviceName = name; } bool WaveBackend::reset() { - ALuint channels=0, bytes=0, chanmask=0; - int isbformat = 0; + uint channels{0}, bytes{0}, chanmask{0}; + bool isbformat{false}; size_t val; fseek(mFile, 0, SEEK_SET); clearerr(mFile); - if(GetConfigValueBool(nullptr, "wave", "bformat", 0)) + if(GetConfigValueBool(nullptr, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; @@ -246,38 +242,43 @@ bool WaveBackend::reset() switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + case DevFmtFloat: + break; } switch(mDevice->FmtChans) { - case DevFmtMono: chanmask = 0x04; break; - case DevFmtStereo: chanmask = 0x01 | 0x02; break; - case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; - case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; - case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break; - case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; - case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; - case DevFmtAmbi3D: - /* .amb output requires FuMa */ - mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); - mDevice->mAmbiLayout = AmbiLayout::FuMa; - mDevice->mAmbiScale = AmbiNorm::FuMa; - isbformat = 1; - chanmask = 0; - break; + case DevFmtMono: chanmask = 0x04; break; + case DevFmtStereo: chanmask = 0x01 | 0x02; break; + case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; + case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; + case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; + case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtX714: + chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 + | 0x8000 | 0x20000; + break; + /* NOTE: Same as 7.1. */ + case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtAmbi3D: + /* .amb output requires FuMa */ + mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); + mDevice->mAmbiLayout = DevAmbiLayout::FuMa; + mDevice->mAmbiScale = DevAmbiScaling::FuMa; + isbformat = true; + chanmask = 0; + break; } bytes = mDevice->bytesFromFmt(); channels = mDevice->channelsFromFmt(); @@ -295,19 +296,19 @@ bool WaveBackend::reset() // 16-bit val, format type id (extensible: 0xFFFE) fwrite16le(0xFFFE, mFile); // 16-bit val, channel count - fwrite16le(static_cast<ALushort>(channels), mFile); + fwrite16le(static_cast<ushort>(channels), mFile); // 32-bit val, frequency fwrite32le(mDevice->Frequency, mFile); // 32-bit val, bytes per second fwrite32le(mDevice->Frequency * channels * bytes, mFile); // 16-bit val, frame size - fwrite16le(static_cast<ALushort>(channels * bytes), mFile); + fwrite16le(static_cast<ushort>(channels * bytes), mFile); // 16-bit val, bits per sample - fwrite16le(static_cast<ALushort>(bytes * 8), mFile); + fwrite16le(static_cast<ushort>(bytes * 8), mFile); // 16-bit val, extra byte count fwrite16le(22, mFile); // 16-bit val, valid bits per sample - fwrite16le(static_cast<ALushort>(bytes * 8), mFile); + fwrite16le(static_cast<ushort>(bytes * 8), mFile); // 32-bit val, channel mask fwrite32le(chanmask, mFile); // 16 byte GUID, sub-type format @@ -326,27 +327,26 @@ bool WaveBackend::reset() } mDataStart = ftell(mFile); - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); - const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; + const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; mBuffer.resize(bufsize); return true; } -bool WaveBackend::start() +void WaveBackend::start() { + if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0) + WARN("Failed to seek on output file\n"); try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return false; } void WaveBackend::stop() @@ -355,14 +355,17 @@ void WaveBackend::stop() return; mThread.join(); - long size{ftell(mFile)}; - if(size > 0) + if(mDataStart > 0) { - long dataLen{size - mDataStart}; - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len + long size{ftell(mFile)}; + if(size > 0) + { + long dataLen{size - mDataStart}; + if(fseek(mFile, 4, SEEK_SET) == 0) + fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len + if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len + } } } @@ -375,20 +378,22 @@ bool WaveBackendFactory::init() bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void WaveBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WaveBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(waveDevice, sizeof(waveDevice)); - break; - case DevProbe::Capture: - break; + case BackendType::Playback: + /* Includes null char. */ + outnames.append(waveDevice, sizeof(waveDevice)); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WaveBackend{device}}; diff --git a/alc/backends/wave.h b/alc/backends/wave.h index b9b62d7f..e768d336 100644 --- a/alc/backends/wave.h +++ b/alc/backends/wave.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WAVE_H #define BACKENDS_WAVE_H -#include "backends/base.h" +#include "base.h" struct WaveBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/backends/winmm.cpp b/alc/backends/winmm.cpp index 649bb345..38e1193f 100644 --- a/alc/backends/winmm.cpp +++ b/alc/backends/winmm.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/winmm.h" +#include "winmm.h" #include <stdlib.h> #include <stdio.h> @@ -28,6 +28,7 @@ #include <windows.h> #include <mmsystem.h> +#include <mmreg.h> #include <array> #include <atomic> @@ -37,13 +38,13 @@ #include <algorithm> #include <functional> -#include "alcmain.h" -#include "alexcpt.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" #include "threads.h" -#include "compat.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 @@ -64,9 +65,9 @@ void ProbePlaybackDevices(void) { PlaybackDevices.clear(); - ALuint numdevs{waveOutGetNumDevs()}; + UINT numdevs{waveOutGetNumDevs()}; PlaybackDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) + for(UINT i{0};i < numdevs;++i) { std::string dname; @@ -95,9 +96,9 @@ void ProbeCaptureDevices(void) { CaptureDevices.clear(); - ALuint numdevs{waveInGetNumDevs()}; + UINT numdevs{waveInGetNumDevs()}; CaptureDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) + for(UINT i{0};i < numdevs;++i) { std::string dname; @@ -124,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; @@ -133,14 +134,14 @@ struct WinMMPlayback final : public BackendBase { int mixerProc(); - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; - bool start() override; + void start() override; void stop() override; - std::atomic<ALuint> mWritable{0u}; + std::atomic<uint> mWritable{0u}; al::semaphore mSem; - ALuint mIdx{0u}; + uint mIdx{0u}; std::array<WAVEHDR,4> mWaveBuffer{}; HWAVEOUT mOutHdl{nullptr}; @@ -180,36 +181,33 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - std::unique_lock<WinMMPlayback> dlock{*this}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { - ALsizei todo = mWritable.load(std::memory_order_acquire); + uint todo{mWritable.load(std::memory_order_acquire)}; if(todo < 1) { - dlock.unlock(); mSem.wait(); - dlock.lock(); continue; } size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); + if(++widx == mWaveBuffer.size()) widx = 0; - aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize); + mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); - mIdx = static_cast<ALuint>(widx); + mIdx = static_cast<uint>(widx); } return 0; } -void WinMMPlayback::open(const ALCchar *name) +void WinMMPlayback::open(const char *name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); @@ -219,51 +217,59 @@ void WinMMPlayback::open(const ALCchar *name) std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; auto DeviceID = static_cast<UINT>(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<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8); - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + HWAVEOUT outHandle{}; + MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC), reinterpret_cast<DWORD_PTR>(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{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res}; + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; } + if(mOutHdl) + waveOutClose(mOutHdl); + mOutHdl = outHandle; + mFormat = format; + mDevice->DeviceName = PlaybackDevices[DeviceID]; } bool WinMMPlayback::reset() { - mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} * + mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} * mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3; + mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u; mDevice->UpdateSize = mDevice->BufferSize / 4; mDevice->Frequency = mFormat.nSamplesPerSec; @@ -295,7 +301,7 @@ bool WinMMPlayback::reset() return false; } - if(mFormat.nChannels == 2) + if(mFormat.nChannels >= 2) mDevice->FmtChans = DevFmtStereo; else if(mFormat.nChannels == 1) mDevice->FmtChans = DevFmtMono; @@ -304,9 +310,9 @@ bool WinMMPlayback::reset() ERR("Unhandled channel count: %d\n", mFormat.nChannels); return false; } - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); - ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()}; + uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; al_free(mWaveBuffer[0].lpData); mWaveBuffer[0] = WAVEHDR{}; @@ -323,25 +329,20 @@ bool WinMMPlayback::reset() return true; } -bool WinMMPlayback::start() +void WinMMPlayback::start() { try { - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); } - ); - mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release); + for(auto &waveHdr : mWaveBuffer) + waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); + mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release); mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; - return true; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return false; } void WinMMPlayback::stop() @@ -352,16 +353,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; @@ -370,15 +369,15 @@ struct WinMMCapture final : public BackendBase { int captureProc(); - void open(const ALCchar *name) override; - bool start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(al::byte *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - std::atomic<ALuint> mReadable{0u}; + std::atomic<uint> mReadable{0u}; al::semaphore mSem; - ALuint mIdx{0}; + uint mIdx{0}; std::array<WAVEHDR,4> mWaveBuffer{}; HWAVEIN mInHdl{nullptr}; @@ -420,16 +419,13 @@ int WinMMCapture::captureProc() { althrd_setname(RECORD_THREAD_NAME); - std::unique_lock<WinMMCapture> dlock{*this}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - ALuint todo{mReadable.load(std::memory_order_acquire)}; + uint todo{mReadable.load(std::memory_order_acquire)}; if(todo < 1) { - dlock.unlock(); mSem.wait(); - dlock.lock(); continue; } @@ -442,14 +438,14 @@ int WinMMCapture::captureProc() mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); - mIdx = static_cast<ALuint>(widx); + mIdx = static_cast<uint>(widx); } return 0; } -void WinMMCapture::open(const ALCchar *name) +void WinMMCapture::open(const char *name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); @@ -459,7 +455,8 @@ void WinMMCapture::open(const ALCchar *name) std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -470,11 +467,12 @@ void WinMMCapture::open(const ALCchar *name) case DevFmtQuad: case DevFmtX51: - case DevFmtX51Rear: case DevFmtX61: case DevFmtX71: + case DevFmtX714: + case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -489,7 +487,7 @@ void WinMMCapture::open(const ALCchar *name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", DevFmtTypeString(mDevice->FmtType)}; } @@ -507,7 +505,7 @@ void WinMMCapture::open(const ALCchar *name) reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC), reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) - throw al::backend_exception{ALC_INVALID_VALUE, "waveInOpen failed: %u", res}; + throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res}; // Ensure each buffer is 50ms each DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; @@ -515,14 +513,14 @@ void WinMMCapture::open(const ALCchar *name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - ALuint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); + uint CapturedDataSize{mDevice->BufferSize}; + CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); - mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false); + mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); al_free(mWaveBuffer[0].lpData); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4)); + mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size())); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();++i) { @@ -534,7 +532,7 @@ void WinMMCapture::open(const ALCchar *name) mDevice->DeviceName = CaptureDevices[DeviceID]; } -bool WinMMCapture::start() +void WinMMCapture::start() { try { for(size_t i{0};i < mWaveBuffer.size();++i) @@ -547,14 +545,11 @@ bool WinMMCapture::start() mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; waveInStart(mInHdl); - return true; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording thread: %s", e.what()}; } - catch(...) { - } - return false; } void WinMMCapture::stop() @@ -576,14 +571,11 @@ void WinMMCapture::stop() mIdx = 0; } -ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void WinMMCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint WinMMCapture::availableSamples() -{ return static_cast<ALCuint>(mRing->readSpace()); } +uint WinMMCapture::availableSamples() +{ return static_cast<uint>(mRing->readSpace()); } } // namespace @@ -594,31 +586,33 @@ bool WinMMBackendFactory::init() bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void WinMMBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WinMMBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const std::string &dname) -> void + std::string outnames; + auto add_device = [&outnames](const std::string &dname) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ if(!dname.empty()) - outnames->append(dname.c_str(), dname.length()+1); + outnames.append(dname.c_str(), dname.length()+1); }; switch(type) { - case DevProbe::Playback: - ProbePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - ProbeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Playback: + ProbePlaybackDevices(); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; + + case BackendType::Capture: + ProbeCaptureDevices(); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + return outnames; } -BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WinMMPlayback{device}}; diff --git a/alc/backends/winmm.h b/alc/backends/winmm.h index e357ec19..45a706aa 100644 --- a/alc/backends/winmm.h +++ b/alc/backends/winmm.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WINMM_H #define BACKENDS_WINMM_H -#include "backends/base.h" +#include "base.h" struct WinMMBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/alc/bformatdec.cpp b/alc/bformatdec.cpp deleted file mode 100644 index 9fbe32b8..00000000 --- a/alc/bformatdec.cpp +++ /dev/null @@ -1,203 +0,0 @@ - -#include "config.h" - -#include "bformatdec.h" - -#include <algorithm> -#include <array> -#include <cassert> -#include <cmath> -#include <iterator> -#include <numeric> - -#include "AL/al.h" - -#include "almalloc.h" -#include "alu.h" -#include "ambdec.h" -#include "filters/splitter.h" -#include "opthelpers.h" - - -namespace { - -constexpr ALfloat Ambi3DDecoderHFScale[MAX_AMBI_ORDER+1] = { - 1.00000000e+00f, 1.00000000e+00f -}; -constexpr ALfloat Ambi3DDecoderHFScale2O[MAX_AMBI_ORDER+1] = { - 7.45355990e-01f, 1.00000000e+00f -}; -constexpr ALfloat Ambi3DDecoderHFScale3O[MAX_AMBI_ORDER+1] = { - 5.89792205e-01f, 8.79693856e-01f -}; - -inline auto GetDecoderHFScales(ALuint order) noexcept -> const ALfloat(&)[MAX_AMBI_ORDER+1] -{ - if(order >= 3) return Ambi3DDecoderHFScale3O; - if(order == 2) return Ambi3DDecoderHFScale2O; - return Ambi3DDecoderHFScale; -} - -inline auto GetAmbiScales(AmbDecScale scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>& -{ - 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 ALuint inchans, - const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mDualBand = allow_2band && (conf->FreqBands == 2); - if(!mDualBand) - mSamples.resize(2); - else - { - ASSUME(inchans > 0); - mSamples.resize(inchans * 2); - mSamplesHF = mSamples.data(); - mSamplesLF = mSamplesHF + inchans; - } - mNumChannels = inchans; - - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+conf->Speakers.size(), 0u, - [](ALuint mask, const ALuint &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ALfloat xover_norm{conf->XOverFreq / static_cast<float>(srate)}; - - const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0}; - const std::array<float,MAX_AMBI_CHANNELS> &coeff_scale = GetAmbiScales(conf->CoeffScale); - const size_t coeff_count{periphonic ? MAX_AMBI_CHANNELS : MAX_AMBI2D_CHANNELS}; - - if(!mDualBand) - { - for(size_t i{0u};i < conf->Speakers.size();i++) - { - ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanmap[i]]; - for(size_t j{0},k{0};j < coeff_count;j++) - { - const size_t l{periphonic ? j : AmbiIndex::From2D[j]}; - if(!(conf->ChanMask&(1u<<l))) continue; - mtx[j] = conf->HFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->HFOrderGain[3] : - (l>=4) ? conf->HFOrderGain[2] : - (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]); - ++k; - } - } - } - else - { - mXOver[0].init(xover_norm); - std::fill(std::begin(mXOver)+1, std::end(mXOver), mXOver[0]); - - const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)}; - for(size_t i{0u};i < conf->Speakers.size();i++) - { - ALfloat (&mtx)[sNumBands][MAX_AMBI_CHANNELS] = mMatrix.Dual[chanmap[i]]; - for(size_t j{0},k{0};j < coeff_count;j++) - { - const size_t l{periphonic ? j : AmbiIndex::From2D[j]}; - if(!(conf->ChanMask&(1u<<l))) continue; - mtx[sHFBand][j] = conf->HFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->HFOrderGain[3] : - (l>=4) ? conf->HFOrderGain[2] : - (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]) * ratio; - mtx[sLFBand][j] = conf->LFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->LFOrderGain[3] : - (l>=4) ? conf->LFOrderGain[2] : - (l>=1) ? conf->LFOrderGain[1] : conf->LFOrderGain[0]) / ratio; - ++k; - } - } - } -} - -BFormatDec::BFormatDec(const ALuint inchans, const ALsizei chancount, - const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS], - const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mSamples.resize(2); - mNumChannels = inchans; - - ASSUME(chancount > 0); - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+chancount, 0u, - [](ALuint mask, const ALuint &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ChannelDec *incoeffs{chancoeffs}; - auto set_coeffs = [this,inchans,&incoeffs](const ALuint chanidx) noexcept -> void - { - ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanidx]; - const ALfloat (&coeffs)[MAX_AMBI_CHANNELS] = *(incoeffs++); - - ASSUME(inchans > 0); - std::copy_n(std::begin(coeffs), inchans, std::begin(mtx)); - }; - std::for_each(chanmap, chanmap+chancount, set_coeffs); -} - - -void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer, - const FloatBufferLine *InSamples, const size_t SamplesToDo) -{ - ASSUME(SamplesToDo > 0); - - if(mDualBand) - { - for(ALuint i{0};i < mNumChannels;i++) - mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i].data(), - SamplesToDo); - - ALfloat (*mixmtx)[sNumBands][MAX_AMBI_CHANNELS]{mMatrix.Dual}; - ALuint enabled{mEnabled}; - for(FloatBufferLine &outbuf : OutBuffer) - { - if LIKELY(enabled&1) - { - const al::span<float> outspan{outbuf.data(), SamplesToDo}; - MixRowSamples(outspan, {(*mixmtx)[sHFBand], mNumChannels}, mSamplesHF->data(), - mSamplesHF->size()); - MixRowSamples(outspan, {(*mixmtx)[sLFBand], mNumChannels}, mSamplesLF->data(), - mSamplesLF->size()); - } - ++mixmtx; - enabled >>= 1; - } - } - else - { - ALfloat (*mixmtx)[MAX_AMBI_CHANNELS]{mMatrix.Single}; - ALuint enabled{mEnabled}; - for(FloatBufferLine &outbuf : OutBuffer) - { - if LIKELY(enabled&1) - MixRowSamples({outbuf.data(), SamplesToDo}, {*mixmtx, mNumChannels}, - InSamples->data(), InSamples->size()); - ++mixmtx; - enabled >>= 1; - } - } -} - - -std::array<ALfloat,MAX_AMBI_ORDER+1> BFormatDec::GetHFOrderScales(const ALuint in_order, const ALuint out_order) noexcept -{ - std::array<ALfloat,MAX_AMBI_ORDER+1> ret{}; - - assert(out_order >= in_order); - - const ALfloat (&target)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(out_order); - const ALfloat (&input)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(in_order); - - for(ALuint i{0};i < in_order+1;++i) - ret[i] = input[i] / target[i]; - - return ret; -} diff --git a/alc/bformatdec.h b/alc/bformatdec.h deleted file mode 100644 index edbb6d50..00000000 --- a/alc/bformatdec.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef BFORMATDEC_H -#define BFORMATDEC_H - -#include <array> -#include <cstddef> - -#include "AL/al.h" - -#include "alcmain.h" -#include "almalloc.h" -#include "alspan.h" -#include "ambidefs.h" -#include "devformat.h" -#include "filters/splitter.h" -#include "vector.h" - -struct AmbDecConf; - - -using ChannelDec = ALfloat[MAX_AMBI_CHANNELS]; - -class BFormatDec { - static constexpr size_t sHFBand{0}; - static constexpr size_t sLFBand{1}; - static constexpr size_t sNumBands{2}; - - bool mDualBand{false}; - ALuint mEnabled{0u}; /* Bitfield of enabled channels. */ - - ALuint mNumChannels{0u}; - union MatrixU { - ALfloat Dual[MAX_OUTPUT_CHANNELS][sNumBands][MAX_AMBI_CHANNELS]; - ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_CHANNELS]; - } mMatrix{}; - - /* NOTE: BandSplitter filters are unused with single-band decoding */ - BandSplitter mXOver[MAX_AMBI_CHANNELS]; - - al::vector<FloatBufferLine, 16> mSamples; - /* These two alias into Samples */ - FloatBufferLine *mSamplesHF{nullptr}; - FloatBufferLine *mSamplesLF{nullptr}; - -public: - BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans, - const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]); - BFormatDec(const ALuint inchans, const ALsizei chancount, - const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS], - const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]); - - /* Decodes the ambisonic input to the given output channels. */ - void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples, - const size_t SamplesToDo); - - /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ - static std::array<ALfloat,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALuint in_order, - const ALuint out_order) noexcept; - - DEF_NEWDEL(BFormatDec) -}; - -#endif /* BFORMATDEC_H */ diff --git a/alc/bs2b.cpp b/alc/bs2b.cpp deleted file mode 100644 index 00207bc0..00000000 --- a/alc/bs2b.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/*- - * Copyright (c) 2005 Boris Mikhaylov - * - * 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 <algorithm> -#include <cmath> -#include <iterator> - -#include "bs2b.h" -#include "math_defs.h" - - -/* Set up all data. */ -static void init(struct bs2b *bs2b) -{ - float Fc_lo, Fc_hi; - float G_lo, G_hi; - float x, g; - - switch(bs2b->level) - { - case BS2B_LOW_CLEVEL: /* Low crossfeed level */ - Fc_lo = 360.0f; - Fc_hi = 501.0f; - G_lo = 0.398107170553497f; - G_hi = 0.205671765275719f; - break; - - case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */ - Fc_lo = 500.0f; - Fc_hi = 711.0f; - G_lo = 0.459726988530872f; - G_hi = 0.228208484414988f; - break; - - case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */ - Fc_lo = 700.0f; - Fc_hi = 1021.0f; - G_lo = 0.530884444230988f; - G_hi = 0.250105790667544f; - break; - - case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */ - Fc_lo = 360.0f; - Fc_hi = 494.0f; - G_lo = 0.316227766016838f; - G_hi = 0.168236228897329f; - break; - - case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */ - Fc_lo = 500.0f; - Fc_hi = 689.0f; - G_lo = 0.354813389233575f; - G_hi = 0.187169483835901f; - break; - - default: /* High easy crossfeed level */ - bs2b->level = BS2B_HIGH_ECLEVEL; - - Fc_lo = 700.0f; - Fc_hi = 975.0f; - G_lo = 0.398107170553497f; - G_hi = 0.205671765275719f; - break; - } /* switch */ - - g = 1.0f / (1.0f - G_hi + G_lo); - - /* $fc = $Fc / $s; - * $d = 1 / 2 / pi / $fc; - * $x = exp(-1 / $d); - */ - x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate)); - bs2b->b1_lo = x; - bs2b->a0_lo = G_lo * (1.0f - x) * g; - - x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate)); - bs2b->b1_hi = x; - bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; - bs2b->a1_hi = -x * g; -} /* init */ - - -/* Exported functions. - * See descriptions in "bs2b.h" - */ - -void bs2b_set_params(struct bs2b *bs2b, int level, int srate) -{ - if(srate <= 0) srate = 1; - - bs2b->level = level; - bs2b->srate = srate; - init(bs2b); -} /* bs2b_set_params */ - -int bs2b_get_level(struct bs2b *bs2b) -{ - return bs2b->level; -} /* bs2b_get_level */ - -int bs2b_get_srate(struct bs2b *bs2b) -{ - return bs2b->srate; -} /* bs2b_get_srate */ - -void bs2b_clear(struct bs2b *bs2b) -{ - std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{}); -} /* bs2b_clear */ - -void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo) -{ - const float a0_lo{bs2b->a0_lo}; - const float b1_lo{bs2b->b1_lo}; - const float a0_hi{bs2b->a0_hi}; - const float a1_hi{bs2b->a1_hi}; - const float b1_hi{bs2b->b1_hi}; - float lsamples[128][2]; - float rsamples[128][2]; - - for(size_t base{0};base < SamplesToDo;) - { - const size_t todo{std::min<size_t>(128, SamplesToDo-base)}; - - /* Process left input */ - float z_lo{bs2b->history[0].lo}; - float z_hi{bs2b->history[0].hi}; - for(size_t i{0};i < todo;i++) - { - lsamples[i][0] = a0_lo*Left[i] + z_lo; - z_lo = b1_lo*lsamples[i][0]; - - lsamples[i][1] = a0_hi*Left[i] + z_hi; - z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1]; - } - bs2b->history[0].lo = z_lo; - bs2b->history[0].hi = z_hi; - - /* Process right input */ - z_lo = bs2b->history[1].lo; - z_hi = bs2b->history[1].hi; - for(size_t i{0};i < todo;i++) - { - rsamples[i][0] = a0_lo*Right[i] + z_lo; - z_lo = b1_lo*rsamples[i][0]; - - rsamples[i][1] = a0_hi*Right[i] + z_hi; - z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1]; - } - bs2b->history[1].lo = z_lo; - bs2b->history[1].hi = z_hi; - - /* Crossfeed */ - for(size_t i{0};i < todo;i++) - *(Left++) = lsamples[i][1] + rsamples[i][0]; - for(size_t i{0};i < todo;i++) - *(Right++) = rsamples[i][1] + lsamples[i][0]; - - base += todo; - } -} /* bs2b_cross_feed */ diff --git a/alc/bs2b.h b/alc/bs2b.h deleted file mode 100644 index df717cd4..00000000 --- a/alc/bs2b.h +++ /dev/null @@ -1,89 +0,0 @@ -/*- - * Copyright (c) 2005 Boris Mikhaylov - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef BS2B_H -#define BS2B_H - -#include "almalloc.h" - -/* Number of crossfeed levels */ -#define BS2B_CLEVELS 3 - -/* Normal crossfeed levels */ -#define BS2B_HIGH_CLEVEL 3 -#define BS2B_MIDDLE_CLEVEL 2 -#define BS2B_LOW_CLEVEL 1 - -/* Easy crossfeed levels */ -#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS -#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS -#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS - -/* Default crossfeed levels */ -#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL -/* Default sample rate (Hz) */ -#define BS2B_DEFAULT_SRATE 44100 - -struct bs2b { - int level; /* Crossfeed level */ - int srate; /* Sample rate (Hz) */ - - /* Lowpass IIR filter coefficients */ - float a0_lo; - float b1_lo; - - /* Highboost IIR filter coefficients */ - float a0_hi; - float a1_hi; - float b1_hi; - - /* Buffer of filter history - * [0] - first channel, [1] - second channel - */ - struct t_last_sample { - float lo; - float hi; - } history[2]; - - DEF_NEWDEL(bs2b) -}; - -/* Clear buffers and set new coefficients with new crossfeed level and sample - * rate values. - * level - crossfeed level of *LEVEL values. - * srate - sample rate by Hz. - */ -void bs2b_set_params(bs2b *bs2b, int level, int srate); - -/* Return current crossfeed level value */ -int bs2b_get_level(bs2b *bs2b); - -/* Return current sample rate value */ -int bs2b_get_srate(bs2b *bs2b); - -/* Clear buffer */ -void bs2b_clear(bs2b *bs2b); - -void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo); - -#endif /* BS2B_H */ diff --git a/alc/compat.h b/alc/compat.h deleted file mode 100644 index 960b4b94..00000000 --- a/alc/compat.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef AL_COMPAT_H -#define AL_COMPAT_H - -#include <string> - -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); - -#endif /* AL_COMPAT_H */ diff --git a/alc/context.cpp b/alc/context.cpp new file mode 100644 index 00000000..e02c549b --- /dev/null +++ b/alc/context.cpp @@ -0,0 +1,1105 @@ + +#include "config.h" + +#include "context.h" + +#include <algorithm> +#include <functional> +#include <limits> +#include <numeric> +#include <stddef.h> +#include <stdexcept> + +#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 <cstring> +#include "alstring.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_STATIC_BUFFER " + "AL_EXT_STEREO_ANGLES " + "AL_LOKI_quadriphonic " + "AL_SOFT_bformat_ex " + "AL_SOFTX_bformat_hoa " + "AL_SOFT_block_alignment " + "AL_SOFT_buffer_length_query " + "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_source_start_delay " + "AL_SOFT_UHJ " + "AL_SOFT_UHJ_ex"; + +} // namespace + + +std::atomic<bool> ALCcontext::sGlobalContextLock{false}; +std::atomic<ALCcontext*> 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<ALCdevice> 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<uint>(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 + eaxUninitialize(); +#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<uint>(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<ALeffectslot>(this); + 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; + + if(sBufferSubDataCompat) + { + std::string extlist{mExtensionList}; + + const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS "); + if(pos != std::string::npos) + extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data"); + else + extlist += " AL_SOFT_buffer_sub_data"; + + mExtensionListOverride = std::move(extlist); + mExtensionList = mExtensionListOverride.c_str(); + } + +#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); + dec_ref(); + } + + ALCcontext *origctx{this}; + if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + { + while(sGlobalContextLock.load()) { + /* Wait to make sure another thread didn't get the context and is + * trying to increment its refcount. + */ + } + dec_ref(); + } + + bool ret{}; + /* First make sure this context exists in the device's list. */ + auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); + if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this))) + { + using ContextArray = al::FlexArray<ContextBase*>; + 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(), + [this](ContextBase *ctx) { return ctx != 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 + if(mEaxNeedsCommit) + eaxCommit(); +#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 { + +template<typename F> +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::eaxIsCapable() const noexcept +{ + return eax_has_enough_aux_sends(); +} + +void ALCcontext::eaxUninitialize() noexcept +{ + if(!mEaxIsInitialized) + return; + + mEaxIsInitialized = false; + mEaxIsTried = false; + mEaxFxSlots.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) +{ + const auto call = create_eax_call( + EaxCallType::set, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size); + + eax_initialize(); + + switch(call.get_property_set_id()) + { + case EaxCallPropertySetId::context: + eax_set(call); + break; + case EaxCallPropertySetId::fx_slot: + case EaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(call); + break; + case EaxCallPropertySetId::source: + eax_dispatch_source(call); + break; + default: + eax_fail_unknown_property_set_id(); + } + mEaxNeedsCommit = true; + + if(!call.is_deferred()) + { + eaxCommit(); + if(!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) +{ + const auto call = create_eax_call( + EaxCallType::get, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size); + + eax_initialize(); + + switch(call.get_property_set_id()) + { + case EaxCallPropertySetId::context: + eax_get(call); + break; + case EaxCallPropertySetId::fx_slot: + case EaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(call); + break; + case EaxCallPropertySetId::source: + eax_dispatch_source(call); + break; + default: + eax_fail_unknown_property_set_id(); + } + + return AL_NO_ERROR; +} + +void ALCcontext::eaxSetLastError() noexcept +{ + mEaxLastError = EAXERR_INVALID_OPERATION; +} + +[[noreturn]] void ALCcontext::eax_fail(const char* message) +{ + throw ContextException{message}; +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id() +{ + eax_fail("Unknown property ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id() +{ + eax_fail("Unknown primary FX Slot ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_property_id() +{ + eax_fail("Unknown property ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_version() +{ + eax_fail("Unknown version."); +} + +void ALCcontext::eax_initialize_extensions() +{ + if(!eax_g_is_enabled) + 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; + + std::string extlist; + extlist.reserve(string_max_capacity); + + if(eaxIsCapable()) + { + extlist += eax1_ext_name; + extlist += ' '; + + extlist += eax2_ext_name; + extlist += ' '; + + extlist += eax3_ext_name; + extlist += ' '; + + extlist += eax4_ext_name; + extlist += ' '; + + extlist += eax5_ext_name; + extlist += ' '; + } + + extlist += eax_x_ram_ext_name; + extlist += ' '; + + extlist += mExtensionList; + + mExtensionListOverride = std::move(extlist); + mExtensionList = mExtensionListOverride.c_str(); +} + +void ALCcontext::eax_initialize() +{ + if(mEaxIsInitialized) + return; + + if(mEaxIsTried) + eax_fail("No EAX."); + + mEaxIsTried = true; + + if(!eax_g_is_enabled) + eax_fail("EAX disabled by a configuration."); + + eax_ensure_compatibility(); + eax_set_defaults(); + eax_context_commit_air_absorbtion_hf(); + eax_update_speaker_configuration(); + eax_initialize_fx_slots(); + + mEaxIsInitialized = 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; + /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to + * suggest with-height surround sound (like HRTF). + */ + case DevFmtX714: return SPEAKERS_7; + /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to + * suggest full-sphere surround sound (like HRTF). + */ + case DevFmtX3D71: return SPEAKERS_5; + /* 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() +{ + mEaxSpeakerConfig = eax_detect_speaker_configuration(); +} + +void ALCcontext::eax_set_last_error_defaults() noexcept +{ + mEaxLastError = EAX_OK; +} + +void ALCcontext::eax_session_set_defaults() noexcept +{ + mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION; + mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; +} + +void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept +{ + props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; + props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; +} + +void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept +{ + eax4_context_set_defaults(state.i); + state.d = state.i; +} + +void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept +{ + props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; + props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; + props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR; +} + +void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept +{ + eax5_context_set_defaults(state.i); + state.d = state.i; +} + +void ALCcontext::eax_context_set_defaults() +{ + eax5_context_set_defaults(mEax123); + eax4_context_set_defaults(mEax4); + eax5_context_set_defaults(mEax5); + mEax = mEax5.i; + mEaxVersion = 5; + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax_set_defaults() +{ + eax_set_last_error_defaults(); + eax_session_set_defaults(); + eax_context_set_defaults(); +} + +void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call) +{ + const auto fx_slot_index = call.get_fx_slot_index(); + if(!fx_slot_index.has_value()) + eax_fail("Invalid fx slot index."); + + auto& fx_slot = eaxGetFxSlot(*fx_slot_index); + if(fx_slot.eax_dispatch(call)) + { + std::lock_guard<std::mutex> source_lock{mSourceLock}; + ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged)); + } +} + +void ALCcontext::eax_dispatch_source(const EaxCall& call) +{ + const auto source_id = call.get_property_al_name(); + std::lock_guard<std::mutex> source_lock{mSourceLock}; + const auto source = ALsource::EaxLookupSource(*this, source_id); + + if (source == nullptr) + eax_fail("Source not found."); + + source->eaxDispatch(call); +} + +void ALCcontext::eax_get_misc(const EaxCall& call) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + case EAXCONTEXT_LASTERROR: + call.set_value<ContextException>(mEaxLastError); + break; + case EAXCONTEXT_SPEAKERCONFIG: + call.set_value<ContextException>(mEaxSpeakerConfig); + break; + case EAXCONTEXT_EAXSESSION: + call.set_value<ContextException>(mEaxSession); + break; + default: + eax_fail_unknown_property_id(); + } +} + +void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_ALLPARAMETERS: + call.set_value<ContextException>(props); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + call.set_value<ContextException>(props.guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + call.set_value<ContextException>(props.flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + call.set_value<ContextException>(props.flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + call.set_value<ContextException>(props.flHFReference); + break; + default: + eax_get_misc(call); + break; + } +} + +void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_ALLPARAMETERS: + call.set_value<ContextException>(props); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + call.set_value<ContextException>(props.guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + call.set_value<ContextException>(props.flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + call.set_value<ContextException>(props.flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + call.set_value<ContextException>(props.flHFReference); + break; + case EAXCONTEXT_MACROFXFACTOR: + call.set_value<ContextException>(props.flMacroFXFactor); + break; + default: + eax_get_misc(call); + break; + } +} + +void ALCcontext::eax_get(const EaxCall& call) +{ + switch(call.get_version()) + { + case 4: eax4_get(call, mEax4.i); break; + case 5: eax5_get(call, mEax5.i); break; + default: eax_fail_unknown_version(); + } +} + +void ALCcontext::eax_context_commit_primary_fx_slot_id() +{ + mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_context_commit_distance_factor() +{ + if(mListener.mMetersPerUnit == mEax.flDistanceFactor) + return; + + mListener.mMetersPerUnit = mEax.flDistanceFactor; + mPropsDirty = true; +} + +void ALCcontext::eax_context_commit_air_absorbtion_hf() +{ + const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); + + if(mAirAbsorptionGainHF == new_value) + return; + + mAirAbsorptionGainHF = new_value; + mPropsDirty = true; +} + +void ALCcontext::eax_context_commit_hf_reference() +{ + // TODO +} + +void ALCcontext::eax_context_commit_macro_fx_factor() +{ + // TODO +} + +void ALCcontext::eax_initialize_fx_slots() +{ + mEaxFxSlots.initialize(*this); + mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_update_sources() +{ + std::unique_lock<std::mutex> source_lock{mSourceLock}; + auto update_source = [](ALsource &source) + { source.eaxCommit(); }; + ForEachSource(this, update_source); +} + +void ALCcontext::eax_set_misc(const EaxCall& call) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + case EAXCONTEXT_SPEAKERCONFIG: + eax_set<Eax5SpeakerConfigValidator>(call, mEaxSpeakerConfig); + break; + case EAXCONTEXT_EAXSESSION: + eax_set<Eax5SessionAllValidator>(call, mEaxSession); + break; + default: + eax_fail_unknown_property_id(); + } +} + +void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) +{ + const auto& src = call.get_value<ContextException, const EAX40CONTEXTPROPERTIES>(); + Eax4AllValidator{}(src); + const auto& dst_i = state.i; + auto& dst_d = state.d; + dst_d = src; + + if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) + mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + mEaxDf |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + mEaxDf |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + mEaxDf |= eax_hf_reference_dirty_bit; +} + +void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_ALLPARAMETERS: + eax4_defer_all(call, state); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>( + call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>( + call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>( + call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>( + call, state, &EAX40CONTEXTPROPERTIES::flHFReference); + break; + default: + eax_set_misc(call); + break; + } +} + +void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) +{ + const auto& src = call.get_value<ContextException, const EAX50CONTEXTPROPERTIES>(); + Eax4AllValidator{}(src); + const auto& dst_i = state.i; + auto& dst_d = state.d; + dst_d = src; + + if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) + mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + mEaxDf |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + mEaxDf |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + mEaxDf |= eax_hf_reference_dirty_bit; + + if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) + mEaxDf |= eax_macro_fx_factor_dirty_bit; +} + +void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) +{ + switch(call.get_property_id()) + { + case EAXCONTEXT_ALLPARAMETERS: + eax5_defer_all(call, state); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>( + call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>( + call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>( + call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>( + call, state, &EAX50CONTEXTPROPERTIES::flHFReference); + break; + case EAXCONTEXT_MACROFXFACTOR: + eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>( + call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + break; + default: + eax_set_misc(call); + break; + } +} + +void ALCcontext::eax_set(const EaxCall& call) +{ + const auto version = call.get_version(); + switch(version) + { + case 4: eax4_defer(call, mEax4); break; + case 5: eax5_defer(call, mEax5); break; + default: eax_fail_unknown_version(); + } + if(version != mEaxVersion) + mEaxDf = ~EaxDirtyFlags(); + mEaxVersion = version; +} + +void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) +{ + if(mEaxDf == EaxDirtyFlags{}) + return; + + eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>( + state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property<eax_distance_factor_dirty_bit>( + state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property<eax_air_absorption_hf_dirty_bit>( + state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property<eax_hf_reference_dirty_bit>( + state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); + + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) +{ + if(mEaxDf == EaxDirtyFlags{}) + return; + + eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>( + state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property<eax_distance_factor_dirty_bit>( + state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property<eax_air_absorption_hf_dirty_bit>( + state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property<eax_hf_reference_dirty_bit>( + state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property<eax_macro_fx_factor_dirty_bit>( + state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax_context_commit() +{ + auto dst_df = EaxDirtyFlags{}; + + switch(mEaxVersion) + { + case 1: + case 2: + case 3: + eax5_context_commit(mEax123, dst_df); + break; + case 4: + eax4_context_commit(mEax4, dst_df); + break; + case 5: + eax5_context_commit(mEax5, dst_df); + break; + } + + if(dst_df == EaxDirtyFlags{}) + return; + + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_primary_fx_slot_id(); + + if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_distance_factor(); + + if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_air_absorbtion_hf(); + + if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_hf_reference(); + + if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_macro_fx_factor(); + + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + eax_update_sources(); +} + +void ALCcontext::eaxCommit() +{ + mEaxNeedsCommit = false; + eax_context_commit(); + eaxCommitFxSlots(); + eax_update_sources(); +} + +namespace { + +class EaxSetException : public EaxException { +public: + explicit EaxSetException(const char* message) + : EaxException{"EAX_SET", message} + {} +}; + +[[noreturn]] void eax_fail_set(const char* message) +{ + throw EaxSetException{message}; +} + +class EaxGetException : public EaxException { +public: + explicit EaxGetException(const char* message) + : EaxException{"EAX_GET", message} + {} +}; + +[[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<std::mutex> 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<std::mutex> 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/alc/context.h b/alc/context.h new file mode 100644 index 00000000..e8efdbf1 --- /dev/null +++ b/alc/context.h @@ -0,0 +1,540 @@ +#ifndef ALC_CONTEXT_H +#define ALC_CONTEXT_H + +#include <atomic> +#include <memory> +#include <mutex> +#include <stdint.h> +#include <utility> + +#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/call.h" +#include "al/eax/exception.h" +#include "al/eax/fx_slot_index.h" +#include "al/eax/fx_slots.h" +#include "al/eax/utils.h" +#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<ALCcontext>, ContextBase { + const al::intrusive_ptr<ALCdevice> mALDevice; + + + bool mPropsDirty{true}; + bool mDeferUpdates{false}; + + std::mutex mPropLock; + + std::atomic<ALenum> 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<SourceSubList> mSourceList; + ALuint mNumSources{0}; + std::mutex mSourceLock; + + al::vector<EffectSlotSubList> mEffectSlotList; + ALuint mNumEffectSlots{0u}; + std::mutex mEffectSlotLock; + + /* Default effect slot */ + std::unique_ptr<ALeffectslot> mDefaultSlot; + + const char *mExtensionList{nullptr}; + + std::string mExtensionListOverride{}; + + + ALCcontext(al::intrusive_ptr<ALCdevice> 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<bool> sGlobalContextLock; + static std::atomic<ALCcontext*> 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 hasEax() const noexcept { return mEaxIsInitialized; } + bool eaxIsCapable() const noexcept; + + void eaxUninitialize() 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 eaxSetLastError() noexcept; + + EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept + { return mEaxPrimaryFxSlotIndex; } + + const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const + { return mEaxFxSlots.get(fx_slot_index); } + ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) + { return mEaxFxSlots.get(fx_slot_index); } + + bool eaxNeedsCommit() const noexcept { return mEaxNeedsCommit; } + void eaxCommit(); + + void eaxCommitFxSlots() + { mEaxFxSlots.commit(); } + +private: + static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0; + static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1; + static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2; + static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3; + static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4; + + using Eax4Props = EAX40CONTEXTPROPERTIES; + + struct Eax4State { + Eax4Props i; // Immediate. + Eax4Props d; // Deferred. + }; + + using Eax5Props = EAX50CONTEXTPROPERTIES; + + struct Eax5State { + Eax5Props i; // Immediate. + Eax5Props d; // Deferred. + }; + + class ContextException : public EaxException + { + public: + explicit ContextException(const char* message) + : EaxException{"EAX_CONTEXT", message} + {} + }; + + struct Eax4PrimaryFxSlotIdValidator { + void operator()(const GUID& guidPrimaryFXSlotID) const + { + if(guidPrimaryFXSlotID != EAX_NULL_GUID && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot3) + { + eax_fail_unknown_primary_fx_slot_id(); + } + } + }; + + struct Eax4DistanceFactorValidator { + void operator()(float flDistanceFactor) const + { + eax_validate_range<ContextException>( + "Distance Factor", + flDistanceFactor, + EAXCONTEXT_MINDISTANCEFACTOR, + EAXCONTEXT_MAXDISTANCEFACTOR); + } + }; + + struct Eax4AirAbsorptionHfValidator { + void operator()(float flAirAbsorptionHF) const + { + eax_validate_range<ContextException>( + "Air Absorption HF", + flAirAbsorptionHF, + EAXCONTEXT_MINAIRABSORPTIONHF, + EAXCONTEXT_MAXAIRABSORPTIONHF); + } + }; + + struct Eax4HfReferenceValidator { + void operator()(float flHFReference) const + { + eax_validate_range<ContextException>( + "HF Reference", + flHFReference, + EAXCONTEXT_MINHFREFERENCE, + EAXCONTEXT_MAXHFREFERENCE); + } + }; + + struct Eax4AllValidator { + void operator()(const EAX40CONTEXTPROPERTIES& all) const + { + Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); + Eax4DistanceFactorValidator{}(all.flDistanceFactor); + Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); + Eax4HfReferenceValidator{}(all.flHFReference); + } + }; + + struct Eax5PrimaryFxSlotIdValidator { + void operator()(const GUID& guidPrimaryFXSlotID) const + { + if(guidPrimaryFXSlotID != EAX_NULL_GUID && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail_unknown_primary_fx_slot_id(); + } + } + }; + + struct Eax5MacroFxFactorValidator { + void operator()(float flMacroFXFactor) const + { + eax_validate_range<ContextException>( + "Macro FX Factor", + flMacroFXFactor, + EAXCONTEXT_MINMACROFXFACTOR, + EAXCONTEXT_MAXMACROFXFACTOR); + } + }; + + struct Eax5AllValidator { + void operator()(const EAX50CONTEXTPROPERTIES& all) const + { + Eax5PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); + Eax4DistanceFactorValidator{}(all.flDistanceFactor); + Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); + Eax4HfReferenceValidator{}(all.flHFReference); + Eax5MacroFxFactorValidator{}(all.flMacroFXFactor); + } + }; + + struct Eax5EaxVersionValidator { + void operator()(unsigned long ulEAXVersion) const + { + eax_validate_range<ContextException>( + "EAX version", + ulEAXVersion, + EAXCONTEXT_MINEAXSESSION, + EAXCONTEXT_MAXEAXSESSION); + } + }; + + struct Eax5MaxActiveSendsValidator { + void operator()(unsigned long ulMaxActiveSends) const + { + eax_validate_range<ContextException>( + "Max Active Sends", + ulMaxActiveSends, + EAXCONTEXT_MINMAXACTIVESENDS, + EAXCONTEXT_MAXMAXACTIVESENDS); + } + }; + + struct Eax5SessionAllValidator { + void operator()(const EAXSESSIONPROPERTIES& all) const + { + Eax5EaxVersionValidator{}(all.ulEAXVersion); + Eax5MaxActiveSendsValidator{}(all.ulMaxActiveSends); + } + }; + + struct Eax5SpeakerConfigValidator { + void operator()(unsigned long ulSpeakerConfig) const + { + eax_validate_range<ContextException>( + "Speaker Config", + ulSpeakerConfig, + EAXCONTEXT_MINSPEAKERCONFIG, + EAXCONTEXT_MAXSPEAKERCONFIG); + } + }; + + bool mEaxIsInitialized{}; + bool mEaxIsTried{}; + + long mEaxLastError{}; + unsigned long mEaxSpeakerConfig{}; + + EaxFxSlotIndex mEaxPrimaryFxSlotIndex{}; + EaxFxSlots mEaxFxSlots{}; + + int mEaxVersion{}; // Current EAX version. + bool mEaxNeedsCommit{}; + EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. + Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. + Eax4State mEax4{}; // EAX4 state. + Eax5State mEax5{}; // EAX5 state. + Eax5Props mEax{}; // Current EAX state. + EAXSESSIONPROPERTIES mEaxSession{}; + + [[noreturn]] static void eax_fail(const char* message); + [[noreturn]] static void eax_fail_unknown_property_set_id(); + [[noreturn]] static void eax_fail_unknown_primary_fx_slot_id(); + [[noreturn]] static void eax_fail_unknown_property_id(); + [[noreturn]] static void eax_fail_unknown_version(); + + // Gets a value from EAX call, + // validates it, + // and updates the current value. + template<typename TValidator, typename TProperty> + static void eax_set(const EaxCall& call, TProperty& property) + { + const auto& value = call.get_value<ContextException, const TProperty>(); + TValidator{}(value); + property = value; + } + + // Gets a new value from EAX call, + // validates it, + // updates the deferred value, + // updates a dirty flag. + template< + typename TValidator, + EaxDirtyFlags TDirtyBit, + typename TMemberResult, + typename TProps, + typename TState> + void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) noexcept + { + const auto& src = call.get_value<ContextException, const TMemberResult>(); + TValidator{}(src); + const auto& dst_i = state.i.*member; + auto& dst_d = state.d.*member; + dst_d = src; + + if(dst_i != dst_d) + mEaxDf |= TDirtyBit; + } + + template< + EaxDirtyFlags TDirtyBit, + typename TMemberResult, + typename TProps, + typename TState> + void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, + TMemberResult TProps::*member) noexcept + { + if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) + { + dst_df |= TDirtyBit; + const auto& src_d = state.d.*member; + state.i.*member = src_d; + mEax.*member = src_d; + } + } + + 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_session_set_defaults() noexcept; + static void eax4_context_set_defaults(Eax4Props& props) noexcept; + static void eax4_context_set_defaults(Eax4State& state) noexcept; + static void eax5_context_set_defaults(Eax5Props& props) noexcept; + static void eax5_context_set_defaults(Eax5State& state) noexcept; + void eax_context_set_defaults(); + void eax_set_defaults(); + + void eax_dispatch_fx_slot(const EaxCall& call); + void eax_dispatch_source(const EaxCall& call); + + void eax_get_misc(const EaxCall& call); + void eax4_get(const EaxCall& call, const Eax4Props& props); + void eax5_get(const EaxCall& call, const Eax5Props& props); + void eax_get(const EaxCall& call); + + void eax_context_commit_primary_fx_slot_id(); + void eax_context_commit_distance_factor(); + void eax_context_commit_air_absorbtion_hf(); + void eax_context_commit_hf_reference(); + void eax_context_commit_macro_fx_factor(); + + void eax_initialize_fx_slots(); + + void eax_update_sources(); + + void eax_set_misc(const EaxCall& call); + void eax4_defer_all(const EaxCall& call, Eax4State& state); + void eax4_defer(const EaxCall& call, Eax4State& state); + void eax5_defer_all(const EaxCall& call, Eax5State& state); + void eax5_defer(const EaxCall& call, Eax5State& state); + void eax_set(const EaxCall& call); + + void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); + void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); + void eax_context_commit(); +#endif // ALSOFT_EAX +}; + +using ContextRef = al::intrusive_ptr<ALCcontext>; + +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/alc/converter.cpp b/alc/converter.cpp deleted file mode 100644 index 553bad58..00000000 --- a/alc/converter.cpp +++ /dev/null @@ -1,360 +0,0 @@ - -#include "config.h" - -#include "converter.h" - -#include <algorithm> -#include <cstdint> -#include <iterator> - -#include "AL/al.h" - -#include "albyte.h" -#include "alu.h" -#include "fpu_modes.h" -#include "mixer/defs.h" - - -namespace { - -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ -template<DevFmtType T> -inline ALfloat LoadSample(typename DevFmtTypeTraits<T>::Type val) noexcept; - -template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val) noexcept -{ return val * (1.0f/128.0f); } -template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val) noexcept -{ return val * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val) noexcept -{ return static_cast<float>(val) * (1.0f/2147483648.0f); } -template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val) noexcept -{ return val; } - -template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val) noexcept -{ return LoadSample<DevFmtByte>(static_cast<ALbyte>(val - 128)); } -template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) noexcept -{ return LoadSample<DevFmtShort>(static_cast<ALshort>(val - 32768)); } -template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) noexcept -{ return LoadSample<DevFmtInt>(static_cast<ALint>(val - 2147483648u)); } - - -template<DevFmtType T> -inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, const size_t srcstep, - const size_t samples) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(size_t i{0u};i < samples;i++) - dst[i] = LoadSample<T>(ssrc[i*srcstep]); -} - -void LoadSamples(ALfloat *dst, const ALvoid *src, const size_t srcstep, const DevFmtType srctype, - const size_t samples) noexcept -{ -#define HANDLE_FMT(T) \ - case T: LoadSampleArray<T>(dst, src, srcstep, samples); break - switch(srctype) - { - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); - } -#undef HANDLE_FMT -} - - -template<DevFmtType T> -inline typename DevFmtTypeTraits<T>::Type StoreSample(ALfloat) noexcept; - -template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val) noexcept -{ return val; } -template<> inline ALint StoreSample<DevFmtInt>(ALfloat val) noexcept -{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } -template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val) noexcept -{ return static_cast<ALshort>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } -template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) noexcept -{ return static_cast<ALbyte>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } - -/* Define unsigned output variations. */ -template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) noexcept -{ return static_cast<ALuint>(StoreSample<DevFmtInt>(val)) + 2147483648u; } -template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) noexcept -{ return static_cast<ALushort>(StoreSample<DevFmtShort>(val) + 32768); } -template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) noexcept -{ return static_cast<ALubyte>(StoreSample<DevFmtByte>(val) + 128); } - -template<DevFmtType T> -inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, const size_t dststep, - const size_t samples) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - SampleType *sdst = static_cast<SampleType*>(dst); - for(size_t i{0u};i < samples;i++) - sdst[i*dststep] = StoreSample<T>(src[i]); -} - - -void StoreSamples(ALvoid *dst, const ALfloat *src, const size_t dststep, const DevFmtType dsttype, - const size_t samples) noexcept -{ -#define HANDLE_FMT(T) \ - case T: StoreSampleArray<T>(dst, src, dststep, samples); break - switch(dsttype) - { - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); - } -#undef HANDLE_FMT -} - - -template<DevFmtType T> -void Mono2Stereo(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(size_t i{0u};i < frames;i++) - dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f; -} - -template<DevFmtType T> -void Stereo2Mono(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(size_t i{0u};i < frames;i++) - dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) * - 0.707106781187f; -} - -} // namespace - -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, - ALuint srcRate, ALuint dstRate, Resampler resampler) -{ - if(numchans < 1 || srcRate < 1 || dstRate < 1) - return nullptr; - - SampleConverterPtr converter{new (FamCount{numchans}) SampleConverter{numchans}}; - converter->mSrcType = srcType; - converter->mDstType = dstType; - converter->mSrcTypeSize = BytesFromDevFmt(srcType); - converter->mDstTypeSize = BytesFromDevFmt(dstType); - - converter->mSrcPrepCount = 0; - converter->mFracOffset = 0; - - /* Have to set the mixer FPU mode since that's what the resampler code expects. */ - FPUCtl mixer_mode{}; - auto step = static_cast<ALuint>( - mind(srcRate*double{FRACTIONONE}/dstRate + 0.5, MAX_PITCH*FRACTIONONE)); - converter->mIncrement = maxu(step, 1); - if(converter->mIncrement == FRACTIONONE) - converter->mResample = Resample_<CopyTag,CTag>; - else - converter->mResample = PrepareResampler(resampler, converter->mIncrement, - &converter->mState); - - return converter; -} - -ALuint SampleConverter::availableOut(ALuint srcframes) const -{ - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(static_cast<ALuint>(-prepcount) >= srcframes) - return 0; - srcframes -= static_cast<ALuint>(-prepcount); - prepcount = 0; - } - - if(srcframes < 1) - { - /* No output samples if there's no input samples. */ - return 0; - } - - if(prepcount < MAX_RESAMPLER_PADDING - && static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= srcframes) - { - /* Not enough input samples to generate an output sample. */ - return 0; - } - - auto DataSize64 = static_cast<uint64_t>(prepcount); - DataSize64 += srcframes; - DataSize64 -= MAX_RESAMPLER_PADDING; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= mFracOffset; - - /* If we have a full prep, we can generate at least one sample. */ - return static_cast<ALuint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE)); -} - -ALuint SampleConverter::convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes) -{ - const ALuint SrcFrameSize{static_cast<ALuint>(mChan.size()) * mSrcTypeSize}; - const ALuint DstFrameSize{static_cast<ALuint>(mChan.size()) * mDstTypeSize}; - const ALuint increment{mIncrement}; - auto SamplesIn = static_cast<const al::byte*>(*src); - ALuint NumSrcSamples{*srcframes}; - - FPUCtl mixer_mode{}; - ALuint pos{0}; - while(pos < dstframes && NumSrcSamples > 0) - { - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(static_cast<ALuint>(-prepcount) >= NumSrcSamples) - { - mSrcPrepCount = static_cast<ALint>(NumSrcSamples) + prepcount; - NumSrcSamples = 0; - break; - } - SamplesIn += SrcFrameSize*static_cast<ALuint>(-prepcount); - NumSrcSamples -= static_cast<ALuint>(-prepcount); - mSrcPrepCount = 0; - continue; - } - ALuint toread{minu(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLER_PADDING)}; - - if(prepcount < MAX_RESAMPLER_PADDING - && static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= toread) - { - /* Not enough input samples to generate an output sample. Store - * what we're given for later. - */ - for(size_t chan{0u};chan < mChan.size();chan++) - LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, - mChan.size(), mSrcType, toread); - - mSrcPrepCount = prepcount + static_cast<ALint>(toread); - NumSrcSamples = 0; - break; - } - - ALfloat *RESTRICT SrcData{mSrcSamples}; - ALfloat *RESTRICT DstData{mDstSamples}; - ALuint DataPosFrac{mFracOffset}; - auto DataSize64 = static_cast<uint64_t>(prepcount); - DataSize64 += toread; - DataSize64 -= MAX_RESAMPLER_PADDING; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= DataPosFrac; - - /* If we have a full prep, we can generate at least one sample. */ - auto DstSize = static_cast<ALuint>( - clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE)); - DstSize = minu(DstSize, dstframes-pos); - - for(size_t chan{0u};chan < mChan.size();chan++) - { - const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan}; - al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan; - - /* Load the previous samples into the source data first, then the - * new samples from the input buffer. - */ - std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData); - LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread); - - /* Store as many prep samples for next time as possible, given the - * number of output samples being generated. - */ - ALuint SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS}; - if(SrcDataEnd >= static_cast<ALuint>(prepcount)+toread) - std::fill(std::begin(mChan[chan].PrevSamples), - std::end(mChan[chan].PrevSamples), 0.0f); - else - { - const size_t len{minz(al::size(mChan[chan].PrevSamples), - static_cast<ALuint>(prepcount)+toread-SrcDataEnd)}; - std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+len, - std::end(mChan[chan].PrevSamples), 0.0f); - } - - /* Now resample, and store the result in the output buffer. */ - const ALfloat *ResampledData{mResample(&mState, SrcData+(MAX_RESAMPLER_PADDING>>1), - DataPosFrac, increment, {DstData, DstSize})}; - - StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize); - } - - /* Update the number of prep samples still available, as well as the - * fractional offset. - */ - DataPosFrac += increment*DstSize; - mSrcPrepCount = mini(prepcount + static_cast<ALint>(toread - (DataPosFrac>>FRACTIONBITS)), - MAX_RESAMPLER_PADDING); - mFracOffset = DataPosFrac & FRACTIONMASK; - - /* Update the src and dst pointers in case there's still more to do. */ - SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS); - NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>FRACTIONBITS)); - - dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize; - pos += DstSize; - } - - *src = SamplesIn; - *srcframes = NumSrcSamples; - - return pos; -} - - -void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALuint frames) const -{ - if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono) - { - switch(mSrcType) - { -#define HANDLE_FMT(T) case T: Stereo2Mono<T>(dst, src, frames); break - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); -#undef HANDLE_FMT - } - } - else if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo) - { - switch(mSrcType) - { -#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); -#undef HANDLE_FMT - } - } - else - LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0)); -} diff --git a/alc/converter.h b/alc/converter.h deleted file mode 100644 index 5842df07..00000000 --- a/alc/converter.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef CONVERTER_H -#define CONVERTER_H - -#include <cstddef> -#include <memory> - -#include "AL/al.h" - -#include "alcmain.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alu.h" -#include "devformat.h" -#include "voice.h" - - -struct SampleConverter { - DevFmtType mSrcType{}; - DevFmtType mDstType{}; - ALuint mSrcTypeSize{}; - ALuint mDstTypeSize{}; - - ALint mSrcPrepCount{}; - - ALuint mFracOffset{}; - ALuint mIncrement{}; - InterpState mState{}; - ResamplerFunc mResample{}; - - alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{}; - alignas(16) ALfloat mDstSamples[BUFFERSIZE]{}; - - struct ChanSamples { - alignas(16) ALfloat PrevSamples[MAX_RESAMPLER_PADDING]; - }; - al::FlexArray<ChanSamples> mChan; - - SampleConverter(size_t numchans) : mChan{numchans} { } - - ALuint convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes); - ALuint availableOut(ALuint srcframes) const; - - DEF_FAM_NEWDEL(SampleConverter, mChan) -}; -using SampleConverterPtr = std::unique_ptr<SampleConverter>; - -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, - ALuint srcRate, ALuint dstRate, Resampler resampler); - - -struct ChannelConverter { - DevFmtType mSrcType; - DevFmtChannels mSrcChans; - DevFmtChannels mDstChans; - - bool is_active() const noexcept { return mSrcChans != mDstChans; } - - void convert(const ALvoid *src, ALfloat *dst, ALuint frames) const; -}; - -#endif /* CONVERTER_H */ diff --git a/alc/cpu_caps.h b/alc/cpu_caps.h deleted file mode 100644 index 64a4ee45..00000000 --- a/alc/cpu_caps.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CPU_CAPS_H -#define CPU_CAPS_H - - -extern int CPUCapFlags; -enum { - CPU_CAP_SSE = 1<<0, - CPU_CAP_SSE2 = 1<<1, - CPU_CAP_SSE3 = 1<<2, - CPU_CAP_SSE4_1 = 1<<3, - CPU_CAP_NEON = 1<<4, -}; - -void FillCPUCaps(int capfilter); - -#endif /* CPU_CAPS_H */ diff --git a/alc/devformat.h b/alc/devformat.h deleted file mode 100644 index 402fb8bd..00000000 --- a/alc/devformat.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef ALC_DEVFORMAT_H -#define ALC_DEVFORMAT_H - -#include <cstdint> - -#include "AL/al.h" -#include "AL/alext.h" - -#include "inprogext.h" - - -enum Channel { - FrontLeft = 0, - FrontRight, - FrontCenter, - LFE, - BackLeft, - BackRight, - BackCenter, - SideLeft, - SideRight, - - UpperFrontLeft, - UpperFrontRight, - UpperBackLeft, - UpperBackRight, - LowerFrontLeft, - LowerFrontRight, - LowerBackLeft, - LowerBackRight, - - Aux0, - Aux1, - Aux2, - Aux3, - Aux4, - Aux5, - Aux6, - Aux7, - Aux8, - Aux9, - Aux10, - Aux11, - Aux12, - Aux13, - Aux14, - Aux15, - - MaxChannels -}; - - -/* Device formats */ -enum DevFmtType : ALenum { - DevFmtByte = ALC_BYTE_SOFT, - DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT, - DevFmtShort = ALC_SHORT_SOFT, - DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT, - DevFmtInt = ALC_INT_SOFT, - DevFmtUInt = ALC_UNSIGNED_INT_SOFT, - DevFmtFloat = ALC_FLOAT_SOFT, - - DevFmtTypeDefault = DevFmtFloat -}; -enum DevFmtChannels : ALenum { - DevFmtMono = ALC_MONO_SOFT, - DevFmtStereo = ALC_STEREO_SOFT, - DevFmtQuad = ALC_QUAD_SOFT, - DevFmtX51 = ALC_5POINT1_SOFT, - DevFmtX61 = ALC_6POINT1_SOFT, - DevFmtX71 = ALC_7POINT1_SOFT, - DevFmtAmbi3D = ALC_BFORMAT3D_SOFT, - - /* Similar to 5.1, except using rear channels instead of sides */ - DevFmtX51Rear = 0x70000000, - - DevFmtChannelsDefault = DevFmtStereo -}; -#define MAX_OUTPUT_CHANNELS (16) - -/* DevFmtType traits, providing the type, etc given a DevFmtType. */ -template<DevFmtType T> -struct DevFmtTypeTraits { }; - -template<> -struct DevFmtTypeTraits<DevFmtByte> { using Type = int8_t; }; -template<> -struct DevFmtTypeTraits<DevFmtUByte> { using Type = uint8_t; }; -template<> -struct DevFmtTypeTraits<DevFmtShort> { using Type = int16_t; }; -template<> -struct DevFmtTypeTraits<DevFmtUShort> { using Type = uint16_t; }; -template<> -struct DevFmtTypeTraits<DevFmtInt> { using Type = int32_t; }; -template<> -struct DevFmtTypeTraits<DevFmtUInt> { using Type = uint32_t; }; -template<> -struct DevFmtTypeTraits<DevFmtFloat> { using Type = float; }; - - -ALuint BytesFromDevFmt(DevFmtType type) noexcept; -ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept; -inline ALuint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALuint ambiorder) noexcept -{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } - -enum class AmbiLayout { - FuMa = ALC_FUMA_SOFT, /* FuMa channel order */ - ACN = ALC_ACN_SOFT, /* ACN channel order */ - - Default = ACN -}; - -enum class AmbiNorm { - FuMa = ALC_FUMA_SOFT, /* FuMa normalization */ - SN3D = ALC_SN3D_SOFT, /* SN3D normalization */ - N3D = ALC_N3D_SOFT, /* N3D normalization */ - - Default = SN3D -}; - -#endif /* ALC_DEVFORMAT_H */ diff --git a/alc/device.cpp b/alc/device.cpp new file mode 100644 index 00000000..66b13c5e --- /dev/null +++ b/alc/device.cpp @@ -0,0 +1,93 @@ + +#include "config.h" + +#include "device.h" + +#include <numeric> +#include <stddef.h> + +#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<uint>(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<uint>(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<uint>(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); +} + +void ALCdevice::enumerateHrtfs() +{ + mHrtfList = EnumerateHrtf(configValue<std::string>(nullptr, "hrtf-paths")); + if(auto defhrtfopt = configValue<std::string>(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 DevFmtX714: + case DevFmtX3D71: + case DevFmtAmbi3D: + break; + } + return OutputMode1::Any; +} diff --git a/alc/device.h b/alc/device.h new file mode 100644 index 00000000..ef50f53e --- /dev/null +++ b/alc/device.h @@ -0,0 +1,165 @@ +#ifndef ALC_DEVICE_H +#define ALC_DEVICE_H + +#include <atomic> +#include <memory> +#include <mutex> +#include <stdint.h> +#include <string> +#include <utility> + +#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<ALCdevice>, 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<BackendBase> 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<std::string> 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<ALCenum> LastError{ALC_NO_ERROR}; + + // Map of Buffers for this device + std::mutex BufferLock; + al::vector<BufferSubList> BufferList; + + // Map of Effects for this device + std::mutex EffectLock; + al::vector<EffectSubList> EffectList; + + // Map of Filters for this device + std::mutex FilterLock; + al::vector<FilterSubList> 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<typename T> + inline al::optional<T> configValue(const char *block, const char *key) = delete; + + DEF_NEWDEL(ALCdevice) +}; + +template<> +inline al::optional<std::string> ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueStr(DeviceName.c_str(), block, key); } +template<> +inline al::optional<int> ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional<uint> ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueUInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional<float> ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueFloat(DeviceName.c_str(), block, key); } +template<> +inline al::optional<bool> ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueBool(DeviceName.c_str(), block, key); } + +#endif diff --git a/alc/effects/autowah.cpp b/alc/effects/autowah.cpp index a79c21d9..4f874ef2 100644 --- a/alc/effects/autowah.cpp +++ b/alc/effects/autowah.cpp @@ -20,63 +20,77 @@ #include "config.h" -#include <cmath> -#include <cstdlib> - #include <algorithm> +#include <array> +#include <cstdlib> +#include <iterator> +#include <utility> + +#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 "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" namespace { -#define MIN_FREQ 20.0f -#define MAX_FREQ 2500.0f -#define Q_FACTOR 5.0f +constexpr float GainScale{31621.0f}; +constexpr float MinFreq{20.0f}; +constexpr float MaxFreq{2500.0f}; +constexpr float QFactor{5.0f}; struct AutowahState final : public EffectState { /* Effect parameters */ - ALfloat mAttackRate; - ALfloat mReleaseRate; - ALfloat mResonanceGain; - ALfloat mPeakGain; - ALfloat mFreqMinNorm; - ALfloat mBandwidthNorm; - ALfloat mEnvDelay; + float mAttackRate; + float mReleaseRate; + float mResonanceGain; + float mPeakGain; + float mFreqMinNorm; + float mBandwidthNorm; + float mEnvDelay; /* Filter components derived from the envelope. */ struct { - ALfloat cos_w0; - ALfloat alpha; - } mEnv[BUFFERSIZE]; + float cos_w0; + float alpha; + } mEnv[BufferLineSize]; struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect filters' history. */ struct { - ALfloat z1, z2; - } Filter; + float z1, z2; + } mFilter; /* Effect gains for each output channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]; - } mChans[MAX_AMBI_CHANNELS]; + float mCurrentGain; + float mTargetGain; + } mChans[MaxAmbiChannels]; /* Effects buffers */ - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; + alignas(16) float mBufferOut[BufferLineSize]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(AutowahState) }; -ALboolean AutowahState::deviceUpdate(const ALCdevice*) +void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ @@ -96,83 +110,92 @@ ALboolean AutowahState::deviceUpdate(const ALCdevice*) for(auto &chan : mChans) { - std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f); - chan.Filter.z1 = 0.0f; - chan.Filter.z2 = 0.0f; + chan.mTargetChannel = InvalidChannelIndex; + chan.mFilter.z1 = 0.0f; + chan.mFilter.z2 = 0.0f; + chan.mCurrentGain = 0.0f; } - - return AL_TRUE; } -void AutowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void AutowahState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; const auto frequency = static_cast<float>(device->Frequency); - const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; + const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency)); mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency)); /* 0-20dB Resonance Peak gain */ mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f); - mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN); - mFreqMinNorm = MIN_FREQ / frequency; - mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / frequency; + mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale); + mFreqMinNorm = MinFreq / frequency; + mBandwidthNorm = (MaxFreq-MinFreq) / frequency; mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + auto set_channel = [this](size_t idx, uint outchan, float outgain) { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } -void AutowahState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) +void AutowahState::process(const size_t samplesToDo, + const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - const ALfloat attack_rate = mAttackRate; - const ALfloat release_rate = mReleaseRate; - const ALfloat res_gain = mResonanceGain; - const ALfloat peak_gain = mPeakGain; - const ALfloat freq_min = mFreqMinNorm; - const ALfloat bandwidth = mBandwidthNorm; - - ALfloat env_delay{mEnvDelay}; + const float attack_rate{mAttackRate}; + const float release_rate{mReleaseRate}; + const float res_gain{mResonanceGain}; + const float peak_gain{mPeakGain}; + const float freq_min{mFreqMinNorm}; + const float bandwidth{mBandwidthNorm}; + + float env_delay{mEnvDelay}; for(size_t i{0u};i < samplesToDo;i++) { - ALfloat w0, sample, a; + float w0, sample, a; /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ sample = peak_gain * std::fabs(samplesIn[0][i]); a = (sample > env_delay) ? attack_rate : release_rate; - env_delay = lerp(sample, env_delay, a); + env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ - w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau(); - mEnv[i].cos_w0 = cosf(w0); - mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR); + w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v<float>*2.0f); + mEnv[i].cos_w0 = std::cos(w0); + mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } mEnvDelay = env_delay; - auto chandata = std::addressof(mChans[0]); + auto chandata = std::begin(mChans); for(const auto &insamples : samplesIn) { + const size_t outidx{chandata->mTargetChannel}; + if(outidx == InvalidChannelIndex) + { + ++chandata; + continue; + } + /* This effectively inlines BiquadFilter_setParams for a peaking * filter and BiquadFilter_processC. The alpha and cosine components * for the filter coefficients were previously calculated with the * envelope. Because the filter changes for each sample, the * coefficients are transient and don't need to be held. */ - ALfloat z1{chandata->Filter.z1}; - ALfloat z2{chandata->Filter.z2}; + float z1{chandata->mFilter.z1}; + float z2{chandata->mFilter.z2}; for(size_t i{0u};i < samplesToDo;i++) { - const ALfloat alpha = mEnv[i].alpha; - const ALfloat cos_w0 = mEnv[i].cos_w0; - ALfloat input, output; - ALfloat a[3], b[3]; + const float alpha{mEnv[i].alpha}; + const float cos_w0{mEnv[i].cos_w0}; + float input, output; + float a[3], b[3]; b[0] = 1.0f + alpha*res_gain; b[1] = -2.0f * cos_w0; @@ -187,109 +210,22 @@ void AutowahState::process(const size_t samplesToDo, const al::span<const FloatB z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); mBufferOut[i] = output; } - chandata->Filter.z1 = z1; - chandata->Filter.z2 = z2; + chandata->mFilter.z1 = z1; + chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains, - chandata->TargetGains, samplesToDo, 0); + MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo); ++chandata; } } -void Autowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range"); - props->Autowah.AttackTime = val; - break; - - case AL_AUTOWAH_RELEASE_TIME: - if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range"); - props->Autowah.ReleaseTime = val; - break; - - case AL_AUTOWAH_RESONANCE: - if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range"); - props->Autowah.Resonance = val; - break; - - case AL_AUTOWAH_PEAK_GAIN: - if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range"); - props->Autowah.PeakGain = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } -} -void Autowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Autowah_setParamf(props, context, param, vals[0]); } - -void Autowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void Autowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -void Autowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } - -} -void Autowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Autowah_getParamf(props, context, param, vals); } - -void Autowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void Autowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Autowah); - - struct AutowahStateFactory final : public EffectStateFactory { - EffectState *create() override { return new AutowahState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Autowah_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new AutowahState{}}; } }; -EffectProps AutowahStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; - props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; - props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; - props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; - return props; -} - } // namespace EffectStateFactory *AutowahStateFactory_getFactory() diff --git a/alc/effects/base.h b/alc/effects/base.h index 6889308d..95695857 100644 --- a/alc/effects/base.h +++ b/alc/effects/base.h @@ -1,175 +1,7 @@ #ifndef EFFECTS_BASE_H #define EFFECTS_BASE_H -#include <cstddef> - -#include "alcmain.h" -#include "almalloc.h" -#include "alspan.h" -#include "atomic.h" -#include "intrusive_ptr.h" - -struct ALeffectslot; - - -union EffectProps { - struct { - // Shared Reverb Properties - ALfloat Density; - ALfloat Diffusion; - ALfloat Gain; - ALfloat GainHF; - ALfloat DecayTime; - ALfloat DecayHFRatio; - ALfloat ReflectionsGain; - ALfloat ReflectionsDelay; - ALfloat LateReverbGain; - ALfloat LateReverbDelay; - ALfloat AirAbsorptionGainHF; - ALfloat RoomRolloffFactor; - bool DecayHFLimit; - - // Additional EAX Reverb Properties - ALfloat GainLF; - ALfloat DecayLFRatio; - ALfloat ReflectionsPan[3]; - ALfloat LateReverbPan[3]; - ALfloat EchoTime; - ALfloat EchoDepth; - ALfloat ModulationTime; - ALfloat ModulationDepth; - ALfloat HFReference; - ALfloat LFReference; - } Reverb; - - struct { - ALfloat AttackTime; - ALfloat ReleaseTime; - ALfloat Resonance; - ALfloat PeakGain; - } Autowah; - - struct { - ALint Waveform; - ALint Phase; - ALfloat Rate; - ALfloat Depth; - ALfloat Feedback; - ALfloat Delay; - } Chorus; /* Also Flanger */ - - struct { - bool OnOff; - } Compressor; - - struct { - ALfloat Edge; - ALfloat Gain; - ALfloat LowpassCutoff; - ALfloat EQCenter; - ALfloat EQBandwidth; - } Distortion; - - struct { - ALfloat Delay; - ALfloat LRDelay; - - ALfloat Damping; - ALfloat Feedback; - - ALfloat Spread; - } Echo; - - struct { - ALfloat LowCutoff; - ALfloat LowGain; - ALfloat Mid1Center; - ALfloat Mid1Gain; - ALfloat Mid1Width; - ALfloat Mid2Center; - ALfloat Mid2Gain; - ALfloat Mid2Width; - ALfloat HighCutoff; - ALfloat HighGain; - } Equalizer; - - struct { - ALfloat Frequency; - ALint LeftDirection; - ALint RightDirection; - } Fshifter; - - struct { - ALfloat Frequency; - ALfloat HighPassCutoff; - ALint Waveform; - } Modulator; - - struct { - ALint CoarseTune; - ALint FineTune; - } Pshifter; - - struct { - ALfloat Rate; - ALint PhonemeA; - ALint PhonemeB; - ALint PhonemeACoarseTuning; - ALint PhonemeBCoarseTuning; - ALint Waveform; - } Vmorpher; - - struct { - ALfloat Gain; - } Dedicated; -}; - - -struct EffectVtable { - void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val); - void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals); - void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val); - void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals); - - void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val); - void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals); - void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val); - void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals); -}; - -#define DEFINE_ALEFFECT_VTABLE(T) \ -const EffectVtable T##_vtable = { \ - T##_setParami, T##_setParamiv, \ - T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, \ - T##_getParamf, T##_getParamfv, \ -} - - -struct EffectTarget { - MixParams *Main; - RealMixParams *RealOut; -}; - -struct EffectState : public al::intrusive_ref<EffectState> { - al::span<FloatBufferLine> mOutTarget; - - - virtual ~EffectState() = default; - - virtual ALboolean deviceUpdate(const ALCdevice *device) = 0; - virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0; - virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) = 0; -}; - - -struct EffectStateFactory { - virtual ~EffectStateFactory() { } - - virtual EffectState *create() = 0; - virtual EffectProps getDefaultProps() const noexcept = 0; - virtual const EffectVtable *getEffectVtable() const noexcept = 0; -}; +#include "core/effects/base.h" EffectStateFactory *NullStateFactory_getFactory(void); @@ -189,5 +21,6 @@ EffectStateFactory* VmorpherStateFactory_getFactory(void); EffectStateFactory *DedicatedStateFactory_getFactory(void); +EffectStateFactory *ConvolutionStateFactory_getFactory(void); #endif /* EFFECTS_BASE_H */ diff --git a/alc/effects/chorus.cpp b/alc/effects/chorus.cpp index 59e05be0..10ccf9f6 100644 --- a/alc/effects/chorus.cpp +++ b/alc/effects/chorus.cpp @@ -21,158 +21,122 @@ #include "config.h" #include <algorithm> +#include <array> #include <climits> -#include <cmath> #include <cstdlib> #include <iterator> -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/efx.h" - -#include "al/auxeffectslot.h" -#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 "ambidefs.h" -#include "effects/base.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 { -static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); -static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); - -enum class WaveForm { - Sinusoid, - Triangle -}; +using uint = unsigned int; -void GetTriangleDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range, - const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo) -{ - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - - ALuint offset{start_offset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint - { - offset = (offset+1)%lfo_range; - const float offset_norm{static_cast<float>(offset) * lfo_scale}; - return static_cast<ALuint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay); - }; - std::generate_n(delays, todo, gen_lfo); -} - -void GetSinusoidDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range, - const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo) -{ - ASSUME(lfo_range > 0); - ASSUME(todo > 0); +struct ChorusState final : public EffectState { + al::vector<float,16> mDelayBuffer; + uint mOffset{0}; - ALuint offset{start_offset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint - { - offset = (offset+1)%lfo_range; - const float offset_norm{static_cast<float>(offset) * lfo_scale}; - return static_cast<ALuint>(fastf2i(std::sin(offset_norm)*depth) + delay); - }; - std::generate_n(delays, todo, gen_lfo); -} + uint mLfoOffset{0}; + uint mLfoRange{1}; + float mLfoScale{0.0f}; + uint mLfoDisp{0}; -struct ChorusState final : public EffectState { - al::vector<ALfloat,16> mSampleBuffer; - ALuint mOffset{0}; + /* Calculated delays to apply to the left and right outputs. */ + uint mModDelays[2][BufferLineSize]; - ALuint mLfoOffset{0}; - ALuint mLfoRange{1}; - ALfloat mLfoScale{0.0f}; - ALuint mLfoDisp{0}; + /* Temp storage for the modulated left and right outputs. */ + alignas(16) float mBuffer[2][BufferLineSize]; - /* Gains for left and right sides */ + /* Gains for left and right outputs. */ struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; /* effect parameters */ - WaveForm mWaveform{}; - ALint mDelay{0}; - ALfloat mDepth{0.0f}; - ALfloat mFeedback{0.0f}; + ChorusWaveform mWaveform{}; + int mDelay{0}; + float mDepth{0.0f}; + float mFeedback{0.0f}; + void calcTriangleDelays(const size_t todo); + void calcSinusoidDelays(const size_t todo); - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(ChorusState) }; -ALboolean ChorusState::deviceUpdate(const ALCdevice *Device) +void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - constexpr ALfloat max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)}; + constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)}; const auto frequency = static_cast<float>(Device->Frequency); const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)}; - if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } + if(maxlen != mDelayBuffer.size()) + decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); + std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f); for(auto &e : mGains) { std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); } - - return AL_TRUE; } -void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target) +void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, + const EffectProps *props, const EffectTarget target) { - constexpr ALsizei mindelay{(MAX_RESAMPLER_PADDING>>1) << FRACTIONBITS}; - - switch(props->Chorus.Waveform) - { - case AL_CHORUS_WAVEFORM_TRIANGLE: - mWaveform = WaveForm::Triangle; - break; - case AL_CHORUS_WAVEFORM_SINUSOID: - mWaveform = WaveForm::Sinusoid; - break; - } + constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ - const ALCdevice *device{Context->mDevice.get()}; + const DeviceBase *device{Context->mDevice}; const auto frequency = static_cast<float>(device->Frequency); - mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay); + mWaveform = props->Chorus.Waveform; + + mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay); mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay), static_cast<float>(mDelay - mindelay)); mFeedback = props->Chorus.Feedback; /* Gains for left and right sides */ - ALfloat coeffs[2][MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]); - CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]); + static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2); + static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); + static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); + auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; + auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target); - ALfloat rate{props->Chorus.Rate}; + float rate{props->Chorus.Rate}; if(!(rate > 0.0f)) { mLfoOffset = 0; @@ -185,340 +149,172 @@ void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, co /* Calculate LFO coefficient (number of samples per cycle). Limit the * max range to avoid overflow when calculating the displacement. */ - ALuint lfo_range{float2uint(minf(frequency/rate + 0.5f, ALfloat{INT_MAX/360 - 180}))}; + uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; switch(mWaveform) { - case WaveForm::Triangle: - mLfoScale = 4.0f / static_cast<float>(mLfoRange); - break; - case WaveForm::Sinusoid: - mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange); - break; + case ChorusWaveform::Triangle: + mLfoScale = 4.0f / static_cast<float>(mLfoRange); + break; + case ChorusWaveform::Sinusoid: + mLfoScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mLfoRange); + break; } /* Calculate lfo phase displacement */ - ALint phase{props->Chorus.Phase}; + int phase{props->Chorus.Phase}; if(phase < 0) phase = 360 + phase; - mLfoDisp = (mLfoRange*static_cast<ALuint>(phase) + 180) / 360; - } -} - -void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) -{ - const size_t bufmask{mSampleBuffer.size()-1}; - const ALfloat feedback{mFeedback}; - const ALuint avgdelay{(static_cast<ALuint>(mDelay) + (FRACTIONONE>>1)) >> FRACTIONBITS}; - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; - ALuint offset{mOffset}; - - for(size_t base{0u};base < samplesToDo;) - { - const size_t todo{minz(256, samplesToDo-base)}; - - ALuint moddelays[2][256]; - if(mWaveform == WaveForm::Sinusoid) - { - GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - else /*if(mWaveform == WaveForm::Triangle)*/ - { - GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - mLfoOffset = (mLfoOffset+static_cast<ALuint>(todo)) % mLfoRange; - - alignas(16) ALfloat temps[2][256]; - for(size_t i{0u};i < todo;i++) - { - // Feed the buffer's input first (necessary for delays < 1). - delaybuf[offset&bufmask] = samplesIn[0][base+i]; - - // Tap for the left output. - ALuint delay{offset - (moddelays[0][i]>>FRACTIONBITS)}; - ALfloat mu{static_cast<float>(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)}; - temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); - - // Tap for the right output. - delay = offset - (moddelays[1][i]>>FRACTIONBITS); - mu = static_cast<float>(moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE); - temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); - - // Accumulate feedback from the average delay of the taps. - delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; - ++offset; - } - - for(ALsizei c{0};c < 2;c++) - MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target, - samplesToDo-base, base); - - base += todo; + mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360; } - - mOffset = offset; } -void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) +void ChorusState::calcTriangleDelays(const size_t todo) { - switch(param) - { - case AL_CHORUS_WAVEFORM: - if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform"); - props->Chorus.Waveform = val; - break; + const uint lfo_range{mLfoRange}; + const float lfo_scale{mLfoScale}; + const float depth{mDepth}; + const int delay{mDelay}; - case AL_CHORUS_PHASE: - if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range"); - props->Chorus.Phase = val; - break; + ASSUME(lfo_range > 0); + ASSUME(todo > 0); - default: - context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); - } -} -void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Chorus_setParami(props, context, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) + auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { - case AL_CHORUS_RATE: - if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range"); - props->Chorus.Rate = val; - break; - - case AL_CHORUS_DEPTH: - if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range"); - props->Chorus.Depth = val; - break; - - case AL_CHORUS_FEEDBACK: - if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range"); - props->Chorus.Feedback = val; - break; - - case AL_CHORUS_DELAY: - if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range"); - props->Chorus.Delay = val; - break; + const float offset_norm{static_cast<float>(offset) * lfo_scale}; + return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay); + }; - default: - context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); + uint offset{mLfoOffset}; + for(size_t i{0};i < todo;) + { + size_t rem{minz(todo-i, lfo_range-offset)}; + do { + mModDelays[0][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; } -} -void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Chorus_setParamf(props, context, param, vals[0]); } -void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) + offset = (mLfoOffset+mLfoDisp) % lfo_range; + for(size_t i{0};i < todo;) { - case AL_CHORUS_WAVEFORM: - *val = props->Chorus.Waveform; - break; - - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); + size_t rem{minz(todo-i, lfo_range-offset)}; + do { + mModDelays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; } -} -void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Chorus_getParami(props, context, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_CHORUS_RATE: - *val = props->Chorus.Rate; - break; - - case AL_CHORUS_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_CHORUS_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_CHORUS_DELAY: - *val = props->Chorus.Delay; - break; - default: - context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); - } + mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range; } -void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Chorus_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Chorus); - -struct ChorusStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ChorusState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; } -}; - -EffectProps ChorusStateFactory::getDefaultProps() const noexcept +void ChorusState::calcSinusoidDelays(const size_t todo) { - EffectProps props{}; - props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM; - props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; - props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; - props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; - return props; -} + const uint lfo_range{mLfoRange}; + const float lfo_scale{mLfoScale}; + const float depth{mDepth}; + const int delay{mDelay}; + ASSUME(lfo_range > 0); + ASSUME(todo > 0); -void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) + auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { - case AL_FLANGER_WAVEFORM: - if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform"); - props->Chorus.Waveform = val; - break; - - case AL_FLANGER_PHASE: - if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range"); - props->Chorus.Phase = val; - break; + const float offset_norm{static_cast<float>(offset) * lfo_scale}; + return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay); + }; - default: - context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); - } -} -void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Flanger_setParami(props, context, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) + uint offset{mLfoOffset}; + for(size_t i{0};i < todo;) { - case AL_FLANGER_RATE: - if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range"); - props->Chorus.Rate = val; - break; - - case AL_FLANGER_DEPTH: - if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range"); - props->Chorus.Depth = val; - break; - - case AL_FLANGER_FEEDBACK: - if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range"); - props->Chorus.Feedback = val; - break; - - case AL_FLANGER_DELAY: - if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range"); - props->Chorus.Delay = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); + size_t rem{minz(todo-i, lfo_range-offset)}; + do { + mModDelays[0][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; } -} -void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Flanger_setParamf(props, context, param, vals[0]); } -void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) + offset = (mLfoOffset+mLfoDisp) % lfo_range; + for(size_t i{0};i < todo;) { - case AL_FLANGER_WAVEFORM: - *val = props->Chorus.Waveform; - break; - - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); + size_t rem{minz(todo-i, lfo_range-offset)}; + do { + mModDelays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; } + + mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range; } -void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Flanger_getParami(props, context, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) + +void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - switch(param) + const size_t bufmask{mDelayBuffer.size()-1}; + const float feedback{mFeedback}; + const uint avgdelay{(static_cast<uint>(mDelay) + MixerFracHalf) >> MixerFracBits}; + float *RESTRICT delaybuf{mDelayBuffer.data()}; + uint offset{mOffset}; + + if(mWaveform == ChorusWaveform::Sinusoid) + calcSinusoidDelays(samplesToDo); + else /*if(mWaveform == ChorusWaveform::Triangle)*/ + calcTriangleDelays(samplesToDo); + + const uint *RESTRICT ldelays{mModDelays[0]}; + const uint *RESTRICT rdelays{mModDelays[1]}; + float *RESTRICT lbuffer{al::assume_aligned<16>(mBuffer[0])}; + float *RESTRICT rbuffer{al::assume_aligned<16>(mBuffer[1])}; + for(size_t i{0u};i < samplesToDo;++i) { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; + // Feed the buffer's input first (necessary for delays < 1). + delaybuf[offset&bufmask] = samplesIn[0][i]; + + // Tap for the left output. + uint delay{offset - (ldelays[i]>>MixerFracBits)}; + float mu{static_cast<float>(ldelays[i]&MixerFracMask) * (1.0f/MixerFracOne)}; + lbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], + delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + + // Tap for the right output. + delay = offset - (rdelays[i]>>MixerFracBits); + mu = static_cast<float>(rdelays[i]&MixerFracMask) * (1.0f/MixerFracOne); + rbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], + delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + + // Accumulate feedback from the average delay of the taps. + delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; + ++offset; + } - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; + MixSamples({lbuffer, samplesToDo}, samplesOut, mGains[0].Current, mGains[0].Target, + samplesToDo, 0); + MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target, + samplesToDo, 0); - default: - context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); - } + mOffset = offset; } -void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Flanger_getParamf(props, context, param, vals); } -DEFINE_ALEFFECT_VTABLE(Flanger); + +struct ChorusStateFactory final : public EffectStateFactory { + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ChorusState{}}; } +}; /* Flanger is basically a chorus with a really short delay. They can both use * the same processing functions, so piggyback flanger on the chorus functions. */ struct FlangerStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ChorusState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ChorusState{}}; } }; -EffectProps FlangerStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM; - props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; - props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; - props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; - return props; -} - } // namespace EffectStateFactory *ChorusStateFactory_getFactory() diff --git a/alc/effects/compressor.cpp b/alc/effects/compressor.cpp index 44ffaaef..0a7ed67a 100644 --- a/alc/effects/compressor.cpp +++ b/alc/effects/compressor.cpp @@ -1,32 +1,56 @@ /** - * OpenAL cross platform audio library + * This file is part of the OpenAL Soft cross platform audio library + * * Copyright (C) 2013 by Anis A. Hireche - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html + * * Neither the name of Spherical-Harmonic-Transform nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" +#include <array> #include <cstdlib> +#include <iterator> +#include <utility> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "vecmat.h" +struct ContextBase; namespace { @@ -40,60 +64,66 @@ namespace { struct CompressorState final : public EffectState { /* Effect gains for each channel */ - ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{}; + struct { + uint mTarget{InvalidChannelIndex}; + float mGain{1.0f}; + } mChans[MaxAmbiChannels]; /* Effect parameters */ - ALboolean mEnabled{AL_TRUE}; - ALfloat mAttackMult{1.0f}; - ALfloat mReleaseMult{1.0f}; - ALfloat mEnvFollower{1.0f}; + bool mEnabled{true}; + float mAttackMult{1.0f}; + float mReleaseMult{1.0f}; + float mEnvFollower{1.0f}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(CompressorState) }; -ALboolean CompressorState::deviceUpdate(const ALCdevice *device) +void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME; - const ALfloat releaseCount = static_cast<ALfloat>(device->Frequency) * RELEASE_TIME; + const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME}; + const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME}; /* Calculate per-sample multipliers to attack and release at the desired * rates. */ mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount); mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount); - - return AL_TRUE; } -void CompressorState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void CompressorState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { mEnabled = props->Compressor.OnOff; mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + auto set_channel = [this](size_t idx, uint outchan, float outgain) { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]); - } + mChans[idx].mTarget = outchan; + mChans[idx].mGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } -void CompressorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) +void CompressorState::process(const size_t samplesToDo, + const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { for(size_t base{0u};base < samplesToDo;) { - ALfloat gains[256]; + float gains[256]; const size_t td{minz(256, samplesToDo-base)}; /* Generate the per-sample gains from the signal envelope. */ - ALfloat env{mEnvFollower}; + float env{mEnvFollower}; if(mEnabled) { for(size_t i{0u};i < td;++i) @@ -101,7 +131,7 @@ void CompressorState::process(const size_t samplesToDo, const al::span<const Flo /* Clamp the absolute amplitude to the defined envelope limits, * then attack or release the envelope to reach it. */ - const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, + const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, AMP_ENVELOPE_MAX)}; if(amplitude > env) env = minf(env*mAttackMult, amplitude); @@ -122,7 +152,7 @@ void CompressorState::process(const size_t samplesToDo, const al::span<const Flo */ for(size_t i{0u};i < td;++i) { - const ALfloat amplitude{1.0f}; + const float amplitude{1.0f}; if(amplitude > env) env = minf(env*mAttackMult, amplitude); else if(amplitude < env) @@ -134,19 +164,22 @@ void CompressorState::process(const size_t samplesToDo, const al::span<const Flo mEnvFollower = env; /* Now compress the signal amplitude to output. */ - auto changains = std::addressof(mGain[0]); + auto chan = std::cbegin(mChans); for(const auto &input : samplesIn) { - const ALfloat *outgains{*(changains++)}; - for(FloatBufferLine &output : samplesOut) + const size_t outidx{chan->mTarget}; + if(outidx != InvalidChannelIndex) { - const ALfloat gain{*(outgains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - for(size_t i{0u};i < td;i++) - output[base+i] += input[base+i] * gains[i] * gain; + const float *RESTRICT src{input.data() + base}; + float *RESTRICT dst{samplesOut[outidx].data() + base}; + const float gain{chan->mGain}; + if(!(std::fabs(gain) > GainSilenceThreshold)) + { + for(size_t i{0u};i < td;i++) + dst[i] += src[i] * gains[i] * gain; + } } + ++chan; } base += td; @@ -154,64 +187,11 @@ void CompressorState::process(const size_t samplesToDo, const al::span<const Flo } -void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range"); - props->Compressor.OnOff = val != AL_FALSE; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Compressor_setParami(props, context, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Compressor_getParami(props, context, param, vals); } -void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Compressor); - - struct CompressorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new CompressorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new CompressorState{}}; } }; -EffectProps CompressorStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - } // namespace EffectStateFactory *CompressorStateFactory_getFactory() diff --git a/alc/effects/convolution.cpp b/alc/effects/convolution.cpp new file mode 100644 index 00000000..7f36c415 --- /dev/null +++ b/alc/effects/convolution.cpp @@ -0,0 +1,636 @@ + +#include "config.h" + +#include <algorithm> +#include <array> +#include <complex> +#include <cstddef> +#include <functional> +#include <iterator> +#include <memory> +#include <stdint.h> +#include <utility> + +#ifdef HAVE_SSE_INTRINSICS +#include <xmmintrin.h> +#elif defined(HAVE_NEON) +#include <arm_neon.h> +#endif + +#include "albyte.h" +#include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "base.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/buffer_storage.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/splitter.h" +#include "core/fmt_traits.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "polyphase_resampler.h" +#include "vector.h" + + +namespace { + +/* Convolution reverb is implemented using a segmented overlap-add method. The + * impulse response is broken up into multiple segments of 128 samples, and + * each segment has an FFT applied with a 256-sample buffer (the latter half + * left silent) to get its frequency-domain response. The resulting response + * has its positive/non-mirrored frequencies saved (129 bins) in each segment. + * + * Input samples are similarly broken up into 128-sample segments, with an FFT + * applied to each new incoming segment to get its 129 bins. A history of FFT'd + * input segments is maintained, equal to the length of the impulse response. + * + * To apply the reverberation, each impulse response segment is convolved with + * its paired input segment (using complex multiplies, far cheaper than FIRs), + * accumulating into a 256-bin FFT buffer. The input history is then shifted to + * align with later impulse response segments for next time. + * + * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- + * sample time-domain response for output, which is split in two halves. The + * first half is the 128-sample output, and the second half is a 128-sample + * (really, 127) delayed extension, which gets added to the output next time. + * Convolving two time-domain responses of lengths N and M results in a time- + * domain signal of length N+M-1, and this holds true regardless of the + * convolution being applied in the frequency domain, so these "overflow" + * samples need to be accounted for. + * + * To avoid a delay with gathering enough input samples to apply an FFT with, + * the first segment is applied directly in the time-domain as the samples come + * in. Once enough have been retrieved, the FFT is applied on the input and + * it's paired with the remaining (FFT'd) filter segments for processing. + */ + + +void LoadSamples(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<T>(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); + /* FIXME: Handle ADPCM decoding here. */ + case FmtIMA4: + case FmtMSADPCM: + std::fill_n(dst, samples, 0.0f); + break; + } +#undef HANDLE_FMT +} + + +inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +{ + switch(scaletype) + { + case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); + case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); + case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::N3D: break; + } + return AmbiScale::FromN3D(); +} + +inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); +} + +inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); + return AmbiIndex::FromACN2D(); +} + + +struct ChanMap { + Channel channel; + float angle; + float elevation; +}; + +constexpr float Deg2Rad(float x) noexcept +{ return static_cast<float>(al::numbers::pi / 180.0 * x); } + + +using complex_f = std::complex<float>; + +constexpr size_t ConvolveUpdateSize{256}; +constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; + + +void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter) +{ +#ifdef HAVE_SSE_INTRINSICS + for(float &output : dst) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < ConvolveUpdateSamples;j+=4) + { + const __m128 coeffs{_mm_load_ps(&filter[j])}; + const __m128 s{_mm_loadu_ps(&src[j])}; + + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + output = _mm_cvtss_f32(r4); + + ++src; + } + +#elif defined(HAVE_NEON) + + for(float &output : dst) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < ConvolveUpdateSamples;j+=4) + r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + + ++src; + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < ConvolveUpdateSamples;++j) + ret += src[j] * filter[j]; + output = ret; + ++src; + } +#endif +} + +struct ConvolutionState final : public EffectState { + FmtChannels mChannels{}; + AmbiLayout mAmbiLayout{}; + AmbiScaling mAmbiScaling{}; + uint mAmbiOrder{}; + + size_t mFifoPos{0}; + std::array<float,ConvolveUpdateSamples*2> mInput{}; + al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter; + al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput; + + alignas(16) std::array<complex_f,ConvolveUpdateSize> mFftBuffer{}; + + size_t mCurrentSegment{0}; + size_t mNumConvolveSegs{0}; + + struct ChannelData { + alignas(16) FloatBufferLine mBuffer{}; + float mHfScale{}, mLfScale{}; + BandSplitter mFilter{}; + float Current[MAX_OUTPUT_CHANNELS]{}; + float Target[MAX_OUTPUT_CHANNELS]{}; + }; + using ChannelDataArray = al::FlexArray<ChannelData>; + std::unique_ptr<ChannelDataArray> mChans; + std::unique_ptr<complex_f[]> mComplexData; + + + ConvolutionState() = default; + ~ConvolutionState() override = default; + + void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); + void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); + void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t) + {&ConvolutionState::NormalMix}; + + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; + + DEF_NEWDEL(ConvolutionState) +}; + +void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut, + const size_t samplesToDo) +{ + for(auto &chan : *mChans) + MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target, + samplesToDo, 0); +} + +void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut, + const size_t samplesToDo) +{ + for(auto &chan : *mChans) + { + const al::span<float> src{chan.mBuffer.data(), samplesToDo}; + chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); + MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); + } +} + + +void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) +{ + using UhjDecoderType = UhjDecoder<512>; + static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; + + constexpr uint MaxConvolveAmbiOrder{1u}; + + mFifoPos = 0; + mInput.fill(0.0f); + decltype(mFilter){}.swap(mFilter); + decltype(mOutput){}.swap(mOutput); + mFftBuffer.fill(complex_f{}); + + mCurrentSegment = 0; + mNumConvolveSegs = 0; + + mChans = nullptr; + mComplexData = nullptr; + + /* An empty buffer doesn't need a convolution filter. */ + if(!buffer || buffer->mSampleLen < 1) return; + + mChannels = buffer->mChannels; + mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; + mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; + mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder); + + constexpr size_t m{ConvolveUpdateSize/2 + 1}; + const auto bytesPerSample = BytesFromFmt(buffer->mType); + const auto realChannels = buffer->channelsFromFmt(); + const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); + + mChans = ChannelDataArray::Create(numChannels); + + /* The impulse response needs to have the same sample rate as the input and + * output. The bsinc24 resampler is decent, but there is high-frequency + * attenuation that some people may be able to pick up on. Since this is + * called very infrequently, go ahead and use the polyphase resampler. + */ + PPhaseResampler resampler; + if(device->Frequency != buffer->mSampleRate) + resampler.init(buffer->mSampleRate, device->Frequency); + const auto resampledCount = static_cast<uint>( + (uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) / + buffer->mSampleRate); + + const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)}; + for(auto &e : *mChans) + e.mFilter = splitter; + + mFilter.resize(numChannels, {}); + mOutput.resize(numChannels, {}); + + /* Calculate the number of segments needed to hold the impulse response and + * the input history (rounded up), and allocate them. Exclude one segment + * which gets applied as a time-domain FIR filter. Make sure at least one + * segment is allocated to simplify handling. + */ + mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; + mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; + + const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; + mComplexData = std::make_unique<complex_f[]>(complex_length); + std::fill_n(mComplexData.get(), complex_length, complex_f{}); + + /* Load the samples from the buffer. */ + const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)}; + auto srcsamples = std::make_unique<float[]>(srclinelength * numChannels); + std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f); + for(size_t c{0};c < numChannels && c < realChannels;++c) + LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c, + realChannels, buffer->mType, buffer->mSampleLen); + + if(IsUHJ(mChannels)) + { + auto decoder = std::make_unique<UhjDecoderType>(); + std::array<float*,4> samples{}; + for(size_t c{0};c < numChannels;++c) + samples[c] = srcsamples.get() + srclinelength*c; + decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen); + } + + auto ressamples = std::make_unique<double[]>(buffer->mSampleLen + + (resampler ? resampledCount : 0)); + complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m; + for(size_t c{0};c < numChannels;++c) + { + /* Resample to match the device. */ + if(resampler) + { + std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, + ressamples.get() + resampledCount); + resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount, + resampledCount, ressamples.get()); + } + else + std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get()); + + /* Store the first segment's samples in reverse in the time-domain, to + * apply as a FIR filter. + */ + const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; + std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(), + [](const double d) noexcept -> float { return static_cast<float>(d); }); + + auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize); + size_t done{first_size}; + for(size_t s{0};s < mNumConvolveSegs;++s) + { + const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; + + auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin()); + done += todo; + std::fill(iter, fftbuffer.end(), std::complex<double>{}); + + forward_fft(al::as_span(fftbuffer)); + filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter); + } + } +} + + +void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps* /*props*/, const EffectTarget target) +{ + /* NOTE: Stereo and Rear are slightly different from normal mixing (as + * defined in alu.cpp). These are 45 degrees from center, rather than the + * 30 degrees used there. + * + * TODO: LFE is not mixed to output. This will require each buffer channel + * to have its own output target since the main mixing buffer won't have an + * LFE channel (due to being B-Format). + */ + static constexpr ChanMap MonoMap[1]{ + { FrontCenter, 0.0f, 0.0f } + }, StereoMap[2]{ + { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) } + }, RearMap[2]{ + { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } + }, QuadMap[4]{ + { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, + { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } + }, X51Map[6]{ + { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } + }, X61Map[7]{ + { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, + { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + }, X71Map[8]{ + { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, + { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + }; + + if(mNumConvolveSegs < 1) UNLIKELY + return; + + mMix = &ConvolutionState::NormalMix; + + for(auto &chan : *mChans) + std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); + const float gain{slot->Gain}; + if(IsAmbisonic(mChannels)) + { + DeviceBase *device{context->mDevice}; + if(mChannels == FmtUHJ2 && !device->mUhjEncoder) + { + mMix = &ConvolutionState::UpsampleMix; + (*mChans)[0].mHfScale = 1.0f; + (*mChans)[0].mLfScale = DecoderBase::sWLFScale; + (*mChans)[1].mHfScale = 1.0f; + (*mChans)[1].mLfScale = DecoderBase::sXYLFScale; + (*mChans)[2].mHfScale = 1.0f; + (*mChans)[2].mLfScale = DecoderBase::sXYLFScale; + } + else if(device->mAmbiOrder > mAmbiOrder) + { + mMix = &ConvolutionState::UpsampleMix; + const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, + device->m2DMixing); + (*mChans)[0].mHfScale = scales[0]; + (*mChans)[0].mLfScale = 1.0f; + for(size_t i{1};i < mChans->size();++i) + { + (*mChans)[i].mHfScale = scales[1]; + (*mChans)[i].mLfScale = 1.0f; + } + } + mOutTarget = target.Main->Buffer; + + auto&& scales = GetAmbiScales(mAmbiScaling); + const uint8_t *index_map{Is2DAmbisonic(mChannels) ? + GetAmbi2DLayout(mAmbiLayout).data() : + GetAmbiLayout(mAmbiLayout).data()}; + + std::array<float,MaxAmbiChannels> coeffs{}; + for(size_t c{0u};c < mChans->size();++c) + { + const size_t acn{index_map[c]}; + coeffs[acn] = scales[acn]; + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target); + coeffs[acn] = 0.0f; + } + } + else + { + DeviceBase *device{context->mDevice}; + al::span<const ChanMap> chanmap{}; + switch(mChannels) + { + case FmtMono: chanmap = MonoMap; break; + case FmtSuperStereo: + case FmtStereo: chanmap = StereoMap; break; + case FmtRear: chanmap = RearMap; break; + case FmtQuad: chanmap = QuadMap; break; + case FmtX51: chanmap = X51Map; break; + case FmtX61: chanmap = X61Map; break; + case FmtX71: chanmap = X71Map; break; + case FmtBFormat2D: + case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + break; + } + + mOutTarget = target.Main->Buffer; + if(device->mRenderMode == RenderMode::Pairwise) + { + auto ScaleAzimuthFront = [](float azimuth, float scale) -> float + { + constexpr float half_pi{al::numbers::pi_v<float>*0.5f}; + const float abs_azi{std::fabs(azimuth)}; + if(!(abs_azi >= half_pi)) + return std::copysign(minf(abs_azi*scale, half_pi), azimuth); + return azimuth; + }; + + for(size_t i{0};i < chanmap.size();++i) + { + if(chanmap[i].channel == LFE) continue; + const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f), + chanmap[i].elevation, 0.0f); + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + } + } + else for(size_t i{0};i < chanmap.size();++i) + { + if(chanmap[i].channel == LFE) continue; + const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f); + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + } + } +} + +void ConvolutionState::process(const size_t samplesToDo, + const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) +{ + if(mNumConvolveSegs < 1) UNLIKELY + return; + + constexpr size_t m{ConvolveUpdateSize/2 + 1}; + size_t curseg{mCurrentSegment}; + auto &chans = *mChans; + + for(size_t base{0u};base < samplesToDo;) + { + const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; + + std::copy_n(samplesIn[0].begin() + base, todo, + mInput.begin()+ConvolveUpdateSamples+mFifoPos); + + /* Apply the FIR for the newly retrieved input samples, and combine it + * with the inverse FFT'd output samples. + */ + for(size_t c{0};c < chans.size();++c) + { + auto buf_iter = chans[c].mBuffer.begin() + base; + apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data()); + + auto fifo_iter = mOutput[c].begin() + mFifoPos; + std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); + } + + mFifoPos += todo; + base += todo; + + /* Check whether the input buffer is filled with new samples. */ + if(mFifoPos < ConvolveUpdateSamples) break; + mFifoPos = 0; + + /* Move the newest input to the front for the next iteration's history. */ + std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); + + /* Calculate the frequency domain response and add the relevant + * frequency bins to the FFT history. + */ + auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); + std::fill(fftiter, mFftBuffer.end(), complex_f{}); + forward_fft(al::as_span(mFftBuffer)); + + std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); + + const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; + for(size_t c{0};c < chans.size();++c) + { + std::fill_n(mFftBuffer.begin(), m, complex_f{}); + + /* Convolve each input segment with its IR filter counterpart + * (aligned in time). + */ + const complex_f *RESTRICT input{&mComplexData[curseg*m]}; + for(size_t s{curseg};s < mNumConvolveSegs;++s) + { + for(size_t i{0};i < m;++i,++input,++filter) + mFftBuffer[i] += *input * *filter; + } + input = mComplexData.get(); + for(size_t s{0};s < curseg;++s) + { + for(size_t i{0};i < m;++i,++input,++filter) + mFftBuffer[i] += *input * *filter; + } + + /* Reconstruct the mirrored/negative frequencies to do a proper + * inverse FFT. + */ + for(size_t i{m};i < ConvolveUpdateSize;++i) + mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]); + + /* Apply iFFT to get the 256 (really 255) samples for output. The + * 128 output samples are combined with the last output's 127 + * second-half samples (and this output's second half is + * subsequently saved for next time). + */ + inverse_fft(al::as_span(mFftBuffer)); + + /* The iFFT'd response is scaled up by the number of bins, so apply + * the inverse to normalize the output. + */ + for(size_t i{0};i < ConvolveUpdateSamples;++i) + mOutput[c][i] = + (mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) * + (1.0f/float{ConvolveUpdateSize}); + for(size_t i{0};i < ConvolveUpdateSamples;++i) + mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real(); + } + + /* Shift the input history. */ + curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1); + } + mCurrentSegment = curseg; + + /* Finally, mix to the output. */ + (this->*mMix)(samplesOut, samplesToDo); +} + + +struct ConvolutionStateFactory final : public EffectStateFactory { + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; } +}; + +} // namespace + +EffectStateFactory *ConvolutionStateFactory_getFactory() +{ + static ConvolutionStateFactory ConvolutionFactory{}; + return &ConvolutionFactory; +} diff --git a/alc/effects/dedicated.cpp b/alc/effects/dedicated.cpp index aa81e13b..047e6761 100644 --- a/alc/effects/dedicated.cpp +++ b/alc/effects/dedicated.cpp @@ -20,70 +20,84 @@ #include "config.h" -#include <cstdlib> -#include <cmath> #include <algorithm> +#include <array> +#include <cstdlib> +#include <iterator> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" +struct ContextBase; namespace { +using uint = unsigned int; + struct DedicatedState final : public EffectState { - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]; + /* The "dedicated" effect can output to the real output, so should have + * gains for all possible output channels and not just the main ambisonic + * buffer. + */ + float mCurrentGains[MAX_OUTPUT_CHANNELS]; + float mTargetGains[MAX_OUTPUT_CHANNELS]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(DedicatedState) }; -ALboolean DedicatedState::deviceUpdate(const ALCdevice*) +void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - return AL_TRUE; } -void DedicatedState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void DedicatedState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain}; + const float Gain{slot->Gain * props->Dedicated.Gain}; - if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT) + if(slot->EffectType == EffectSlotType::DedicatedLFE) { - const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : - GetChannelIdxByName(*target.RealOut, LFE)}; - if(idx != INVALID_CHANNEL_INDEX) + const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) { mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } } - else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE) + else if(slot->EffectType == EffectSlotType::DedicatedDialog) { /* Dialog goes to the front-center speaker if it exists, otherwise it * plays from the front-center location. */ - const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : - GetChannelIdxByName(*target.RealOut, FrontCenter)}; - if(idx != INVALID_CHANNEL_INDEX) + const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) { mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } else { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); } } } @@ -95,62 +109,11 @@ void DedicatedState::process(const size_t samplesToDo, const al::span<const Floa } -void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - if(!(val >= 0.0f && std::isfinite(val))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range"); - props->Dedicated.Gain = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Dedicated_setParamf(props, context, param, vals[0]); } - -void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Dedicated_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Dedicated); - - struct DedicatedStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DedicatedState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new DedicatedState{}}; } }; -EffectProps DedicatedStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; -} - } // namespace EffectStateFactory *DedicatedStateFactory_getFactory() diff --git a/alc/effects/distortion.cpp b/alc/effects/distortion.cpp index 7dd43008..b4e2167e 100644 --- a/alc/effects/distortion.cpp +++ b/alc/effects/distortion.cpp @@ -20,82 +20,90 @@ #include "config.h" -#include <cmath> +#include <algorithm> +#include <array> #include <cstdlib> - -#include <cmath> - -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "filters/biquad.h" +#include <iterator> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ - ALfloat mGain[MAX_OUTPUT_CHANNELS]{}; + float mGain[MaxAmbiChannels]{}; /* Effect parameters */ BiquadFilter mLowpass; BiquadFilter mBandpass; - ALfloat mAttenuation{}; - ALfloat mEdgeCoeff{}; + float mAttenuation{}; + float mEdgeCoeff{}; - ALfloat mBuffer[2][BUFFERSIZE]{}; + alignas(16) float mBuffer[2][BufferLineSize]{}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(DistortionState) }; -ALboolean DistortionState::deviceUpdate(const ALCdevice*) +void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mLowpass.clear(); mBandpass.clear(); - return AL_TRUE; } -void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void DistortionState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ - const ALfloat edge{ - minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)}; + const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge), + 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); - ALfloat cutoff{props->Distortion.LowpassCutoff}; + float cutoff{props->Distortion.LowpassCutoff}; /* Bandwidth value is constant in octaves. */ - ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; - /* Multiply sampling frequency by the amount of oversampling done during + float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; + /* Divide normalized frequency by the amount of oversampling done during * processing. */ - auto frequency = static_cast<ALfloat>(device->Frequency); - mLowpass.setParams(BiquadType::LowPass, 1.0f, cutoff / (frequency*4.0f), - mLowpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth)); + auto frequency = static_cast<float>(device->Frequency); + mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); cutoff = props->Distortion.EQCenter; /* Convert bandwidth in Hz to octaves. */ bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f); - mBandpass.setParams(BiquadType::BandPass, 1.0f, cutoff / (frequency*4.0f), - mBandpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth)); + mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain); + ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain); } void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - const ALfloat fc{mEdgeCoeff}; + const float fc{mEdgeCoeff}; for(size_t base{0u};base < samplesToDo;) { /* Perform 4x oversampling to avoid aliasing. Oversampling greatly @@ -103,7 +111,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo * bandpass filters using high frequencies, at which classic IIR * filters became unstable. */ - size_t todo{minz(BUFFERSIZE, (samplesToDo-base) * 4)}; + size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)}; /* Fill oversample buffer using zero stuffing. Multiply the sample by * the amount of oversampling to maintain the signal's power. @@ -116,36 +124,35 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo * (which is fortunately first step of distortion). So combine three * operations into the one. */ - mLowpass.process(mBuffer[1], mBuffer[0], todo); + mLowpass.process({mBuffer[0], todo}, mBuffer[1]); /* Second step, do distortion using waveshaper function to emulate * signal processing during tube overdriving. Three steps of * waveshaping are intended to modify waveform without boost/clipping/ * attenuation process. */ - for(size_t i{0u};i < todo;i++) + auto proc_sample = [fc](float smp) -> float { - ALfloat smp{mBuffer[1][i]}; - - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)); - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)) * -1.0f; - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)); - - mBuffer[0][i] = smp; - } + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f; + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + return smp; + }; + std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]), + proc_sample); /* Third step, do bandpass filtering of distorted signal. */ - mBandpass.process(mBuffer[1], mBuffer[0], todo); + mBandpass.process({mBuffer[0], todo}, mBuffer[1]); todo >>= 2; - const ALfloat *outgains{mGain}; + const float *outgains{mGain}; for(FloatBufferLine &output : samplesOut) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ - const ALfloat gain{*(outgains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) + const float gain{*(outgains++)}; + if(!(std::fabs(gain) > GainSilenceThreshold)) continue; for(size_t i{0u};i < todo;i++) @@ -157,106 +164,11 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo } -void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range"); - props->Distortion.Edge = val; - break; - - case AL_DISTORTION_GAIN: - if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range"); - props->Distortion.Gain = val; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range"); - props->Distortion.LowpassCutoff = val; - break; - - case AL_DISTORTION_EQCENTER: - if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range"); - props->Distortion.EQCenter = val; - break; - - case AL_DISTORTION_EQBANDWIDTH: - if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range"); - props->Distortion.EQBandwidth = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param); - } -} -void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Distortion_setParamf(props, context, param, vals[0]); } - -void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param); - } -} -void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Distortion_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Distortion); - - struct DistortionStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DistortionState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new DistortionState{}}; } }; -EffectProps DistortionStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; - props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; - props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; - props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; - props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; - return props; -} - } // namespace EffectStateFactory *DistortionStateFactory_getFactory() diff --git a/alc/effects/echo.cpp b/alc/effects/echo.cpp index a9213df5..a69529dc 100644 --- a/alc/effects/echo.cpp +++ b/alc/effects/echo.cpp @@ -20,24 +20,36 @@ #include "config.h" -#include <cmath> -#include <cstdlib> - #include <algorithm> - -#include "al/auxeffectslot.h" -#include "al/filter.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "filters/biquad.h" +#include <array> +#include <cstdlib> +#include <iterator> +#include <tuple> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "vector.h" namespace { +using uint = unsigned int; + +constexpr float LowpassFreqRef{5000.0f}; + struct EchoState final : public EffectState { - al::vector<ALfloat,16> mSampleBuffer; + al::vector<float,16> mSampleBuffer; // The echo is two tap. The delay is the number of samples from before the // current offset @@ -48,35 +60,34 @@ struct EchoState final : public EffectState { /* The panning gains for the two taps */ struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; BiquadFilter mFilter; - ALfloat mFeedGain{0.0f}; + float mFeedGain{0.0f}; - alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE]; + alignas(16) float mTempBuffer[2][BufferLineSize]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(EchoState) }; -ALboolean EchoState::deviceUpdate(const ALCdevice *Device) +void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { const auto frequency = static_cast<float>(Device->Frequency); // Use the next power of 2 for the buffer length, so the tap offsets can be // wrapped using a mask instead of a modulo - const ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) + - float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))}; + const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) + + float2uint(EchoMaxLRDelay*frequency + 0.5f))}; if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } + al::vector<float,16>(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) @@ -84,44 +95,41 @@ ALboolean EchoState::deviceUpdate(const ALCdevice *Device) std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); } - - return AL_TRUE; } -void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void EchoState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; - const auto frequency = static_cast<ALfloat>(device->Frequency); + const DeviceBase *device{context->mDevice}; + const auto frequency = static_cast<float>(device->Frequency); mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1); mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay; - const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ - mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency, - mFilter.rcpQFromSlope(gainhf, 1.0f)); + const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ + mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f); mFeedGain = props->Echo.Feedback; /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */ - const ALfloat angle{std::asin(props->Echo.Spread)}; + const float angle{std::asin(props->Echo.Spread)}; - ALfloat coeffs[2][MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]); - CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]); + const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f); + const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target); + ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target); } void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { const size_t mask{mSampleBuffer.size()-1}; - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; + float *RESTRICT delaybuf{mSampleBuffer.data()}; size_t offset{mOffset}; size_t tap1{offset - mTap[0].delay}; size_t tap2{offset - mTap[1].delay}; - ALfloat z1, z2; + float z1, z2; ASSUME(samplesToDo > 0); @@ -152,112 +160,17 @@ void EchoState::process(const size_t samplesToDo, const al::span<const FloatBuff mFilter.setComponents(z1, z2); mOffset = offset; - for(ALsizei c{0};c < 2;c++) + for(size_t c{0};c < 2;c++) MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo, 0); } -void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_ECHO_DELAY: - if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range"); - props->Echo.Delay = val; - break; - - case AL_ECHO_LRDELAY: - if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range"); - props->Echo.LRDelay = val; - break; - - case AL_ECHO_DAMPING: - if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range"); - props->Echo.Damping = val; - break; - - case AL_ECHO_FEEDBACK: - if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range"); - props->Echo.Feedback = val; - break; - - case AL_ECHO_SPREAD: - if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range"); - props->Echo.Spread = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Echo_setParamf(props, context, param, vals[0]); } - -void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Echo_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Echo); - - struct EchoStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EchoState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new EchoState{}}; } }; -EffectProps EchoStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; - props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; - props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; - props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; - props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; - return props; -} - } // namespace EffectStateFactory *EchoStateFactory_getFactory() diff --git a/alc/effects/equalizer.cpp b/alc/effects/equalizer.cpp index 929bff14..50bec4ad 100644 --- a/alc/effects/equalizer.cpp +++ b/alc/effects/equalizer.cpp @@ -20,18 +20,25 @@ #include "config.h" -#include <cmath> -#include <cstdlib> - #include <algorithm> +#include <array> +#include <cstdlib> #include <functional> - -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" +#include <iterator> +#include <utility> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { @@ -80,255 +87,114 @@ namespace { struct EqualizerState final : public EffectState { struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - BiquadFilter filter[4]; + BiquadFilter mFilter[4]; /* Effect gains for each channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; + float mCurrentGain{}; + float mTargetGain{}; + } mChans[MaxAmbiChannels]; - ALfloat mSampleBuffer[BUFFERSIZE]{}; + alignas(16) FloatBufferLine mSampleBuffer{}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(EqualizerState) }; -ALboolean EqualizerState::deviceUpdate(const ALCdevice*) +void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - std::for_each(std::begin(e.filter), std::end(e.filter), - std::mem_fn(&BiquadFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = InvalidChannelIndex; + std::for_each(std::begin(e.mFilter), std::end(e.mFilter), + std::mem_fn(&BiquadFilter::clear)); + e.mCurrentGain = 0.0f; } - return AL_TRUE; } -void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; - auto frequency = static_cast<ALfloat>(device->Frequency); - ALfloat gain, f0norm; + const DeviceBase *device{context->mDevice}; + auto frequency = static_cast<float>(device->Frequency); + float gain, f0norm; /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, - * meaning its dB needs to be doubled for the shelf or peak to reach the - * provided gain. + * while the effect property gains are for the shelf/peak itself. So the + * property gains need their dB halved (sqrt of linear gain) for the + * shelf/peak to reach the provided gain. */ - gain = maxf(std::sqrt(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */ - f0norm = props->Equalizer.LowCutoff/frequency; - mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm, - BiquadFilter::rcpQFromSlope(gain, 0.75f)); + gain = std::sqrt(props->Equalizer.LowGain); + f0norm = props->Equalizer.LowCutoff / frequency; + mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); - gain = maxf(std::sqrt(props->Equalizer.Mid1Gain), 0.0625f); - f0norm = props->Equalizer.Mid1Center/frequency; - mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm, - BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid1Width)); + gain = std::sqrt(props->Equalizer.Mid1Gain); + f0norm = props->Equalizer.Mid1Center / frequency; + mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props->Equalizer.Mid1Width); - gain = maxf(std::sqrt(props->Equalizer.Mid2Gain), 0.0625f); - f0norm = props->Equalizer.Mid2Center/frequency; - mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm, - BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid2Width)); + gain = std::sqrt(props->Equalizer.Mid2Gain); + f0norm = props->Equalizer.Mid2Center / frequency; + mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props->Equalizer.Mid2Width); - gain = maxf(std::sqrt(props->Equalizer.HighGain), 0.0625f); - f0norm = props->Equalizer.HighCutoff/frequency; - mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm, - BiquadFilter::rcpQFromSlope(gain, 0.75f)); + gain = std::sqrt(props->Equalizer.HighGain); + f0norm = props->Equalizer.HighCutoff / frequency; + mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ for(size_t i{1u};i < slot->Wet.Buffer.size();++i) { - mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]); - mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]); - mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]); - mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]); + mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]); + mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]); + mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]); + mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]); } mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + auto set_channel = [this](size_t idx, uint outchan, float outgain) { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - auto chandata = std::addressof(mChans[0]); + const al::span<float> buffer{mSampleBuffer.data(), samplesToDo}; + auto chan = std::begin(mChans); for(const auto &input : samplesIn) { - chandata->filter[0].process(mSampleBuffer, input.data(), samplesToDo); - chandata->filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo); - chandata->filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo); - chandata->filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo); - - MixSamples({mSampleBuffer, samplesToDo}, samplesOut, chandata->CurrentGains, - chandata->TargetGains, samplesToDo, 0); - ++chandata; - } -} - - -void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range"); - props->Equalizer.LowGain = val; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range"); - props->Equalizer.LowCutoff = val; - break; - - case AL_EQUALIZER_MID1_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range"); - props->Equalizer.Mid1Gain = val; - break; - - case AL_EQUALIZER_MID1_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range"); - props->Equalizer.Mid1Center = val; - break; - - case AL_EQUALIZER_MID1_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range"); - props->Equalizer.Mid1Width = val; - break; - - case AL_EQUALIZER_MID2_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range"); - props->Equalizer.Mid2Gain = val; - break; - - case AL_EQUALIZER_MID2_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range"); - props->Equalizer.Mid2Center = val; - break; - - case AL_EQUALIZER_MID2_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range"); - props->Equalizer.Mid2Width = val; - break; - - case AL_EQUALIZER_HIGH_GAIN: - if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range"); - props->Equalizer.HighGain = val; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range"); - props->Equalizer.HighCutoff = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); - } -} -void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Equalizer_setParamf(props, context, param, vals[0]); } - -void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); + const size_t outidx{chan->mTargetChannel}; + if(outidx != InvalidChannelIndex) + { + const al::span<const float> inbuf{input.data(), samplesToDo}; + DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin()); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin()); + + MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + samplesToDo); + } + ++chan; } } -void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Equalizer_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Equalizer); struct EqualizerStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EqualizerState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new EqualizerState{}}; } }; -EffectProps EqualizerStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; - props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; - props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; - props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; - props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; - props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; - props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; - props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; - props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; - props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; - return props; -} - } // namespace EffectStateFactory *EqualizerStateFactory_getFactory() diff --git a/alc/effects/fshifter.cpp b/alc/effects/fshifter.cpp index 1b935047..3e6a7385 100644 --- a/alc/effects/fshifter.cpp +++ b/alc/effects/fshifter.cpp @@ -20,207 +20,219 @@ #include "config.h" -#include <cmath> -#include <cstdlib> +#include <algorithm> #include <array> +#include <cmath> #include <complex> -#include <algorithm> - -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" +#include <cstdlib> +#include <iterator> +#include "alc/effects/base.h" #include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + namespace { +using uint = unsigned int; using complex_d = std::complex<double>; -#define HIL_SIZE 1024 -#define OVERSAMP (1<<2) +constexpr size_t HilSize{1024}; +constexpr size_t HilHalfSize{HilSize >> 1}; +constexpr size_t OversampleFactor{4}; -#define HIL_STEP (HIL_SIZE / OVERSAMP) -#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1)) +static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); +constexpr size_t HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ -/* Making this constexpr seems to require C++14. */ -std::array<ALdouble,HIL_SIZE> InitHannWindow() -{ - std::array<ALdouble,HIL_SIZE> ret; - /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ - for(size_t i{0};i < HIL_SIZE>>1;i++) +struct Windower { + alignas(16) std::array<double,HilSize> mData; + + Windower() { - constexpr double scale{al::MathDefs<double>::Pi() / double{HIL_SIZE-1}}; - const double val{std::sin(static_cast<double>(i) * scale)}; - ret[i] = ret[HIL_SIZE-1-i] = val * val; + /* Create lookup table of the Hann window for the desired size. */ + for(size_t i{0};i < HilHalfSize;i++) + { + constexpr double scale{al::numbers::pi / double{HilSize}}; + const double val{std::sin((static_cast<double>(i)+0.5) * scale)}; + mData[i] = mData[HilSize-1-i] = val * val; + } } - return ret; -} -alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow(); +}; +const Windower gWindow{}; struct FshifterState final : public EffectState { /* Effect parameters */ - size_t mCount{}; - ALsizei mPhaseStep[2]{}; - ALsizei mPhase[2]{}; - ALdouble mSign[2]{}; - + size_t mCount{}; + size_t mPos{}; + std::array<uint,2> mPhaseStep{}; + std::array<uint,2> mPhase{}; + std::array<double,2> mSign{}; - /*Effects buffers*/ - ALfloat mInFIFO[HIL_SIZE]{}; - complex_d mOutFIFO[HIL_SIZE]{}; - complex_d mOutputAccum[HIL_SIZE]{}; - complex_d mAnalytic[HIL_SIZE]{}; - complex_d mOutdata[BUFFERSIZE]{}; + /* Effects buffers */ + std::array<double,HilSize> mInFIFO{}; + std::array<complex_d,HilStep> mOutFIFO{}; + std::array<complex_d,HilSize> mOutputAccum{}; + std::array<complex_d,HilSize> mAnalytic{}; + std::array<complex_d,BufferLineSize> mOutdata{}; - alignas(16) ALfloat mBufferOut[BUFFERSIZE]{}; + alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(FshifterState) }; -ALboolean FshifterState::deviceUpdate(const ALCdevice*) +void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; + mCount = 0; + mPos = HilSize - HilStep; - std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0); - std::fill(std::begin(mPhase), std::end(mPhase), 0); - std::fill(std::begin(mSign), std::end(mSign), 1.0); - std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f); - std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{}); - std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{}); - std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{}); + mPhaseStep.fill(0u); + mPhase.fill(0u); + mSign.fill(1.0); + mInFIFO.fill(0.0); + mOutFIFO.fill(complex_d{}); + mOutputAccum.fill(complex_d{}); + mAnalytic.fill(complex_d{}); for(auto &gain : mGains) { std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f); std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f); } - - return AL_TRUE; } -void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void FshifterState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; - ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)}; - mPhaseStep[0] = mPhaseStep[1] = fastf2i(minf(step, 0.5f) * FRACTIONONE); + const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)}; + mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne); switch(props->Fshifter.LeftDirection) { - case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: + case FShifterDirection::Down: mSign[0] = -1.0; break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_UP: + case FShifterDirection::Up: mSign[0] = 1.0; break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: + case FShifterDirection::Off: mPhase[0] = 0; mPhaseStep[0] = 0; break; } - switch (props->Fshifter.RightDirection) + switch(props->Fshifter.RightDirection) { - case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: + case FShifterDirection::Down: mSign[1] = -1.0; break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_UP: + case FShifterDirection::Up: mSign[1] = 1.0; break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: + case FShifterDirection::Off: mPhase[1] = 0; mPhaseStep[1] = 0; break; } - ALfloat coeffs[2][MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({-1.0f, 0.0f, -1.0f}, 0.0f, coeffs[0]); - CalcDirectionCoeffs({ 1.0f, 0.0f, -1.0f}, 0.0f, coeffs[1]); + static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2); + static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); + static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); + auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; + auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target); } void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - static constexpr complex_d complex_zero{0.0, 0.0}; - ALfloat *RESTRICT BufferOut = mBufferOut; - size_t j, k; - for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(HIL_SIZE-mCount, samplesToDo-base)}; - - ASSUME(todo > 0); + size_t todo{minz(HilStep-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ - k = mCount; - for(j = 0;j < todo;j++,k++) - { - mInFIFO[k] = samplesIn[0][base+j]; - mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY]; - } - mCount += todo; - base += todo; + const size_t pos{mPos}; + size_t count{mCount}; + do { + mInFIFO[pos+count] = samplesIn[0][base]; + mOutdata[base] = mOutFIFO[count]; + ++base; ++count; + } while(--todo); + mCount = count; /* Check whether FIFO buffer is filled */ - if(mCount < HIL_SIZE) continue; - mCount = FIFO_LATENCY; + if(mCount < HilStep) break; + mCount = 0; + mPos = (mPos+HilStep) & (HilSize-1); /* Real signal windowing and store in Analytic buffer */ - for(k = 0;k < HIL_SIZE;k++) - { - mAnalytic[k].real(mInFIFO[k] * HannWindow[k]); - mAnalytic[k].imag(0.0); - } + for(size_t src{mPos}, k{0u};src < HilSize;++src,++k) + mAnalytic[k] = mInFIFO[src]*gWindow.mData[k]; + for(size_t src{0u}, k{HilSize-mPos};src < mPos;++src,++k) + mAnalytic[k] = mInFIFO[src]*gWindow.mData[k]; /* Processing signal by Discrete Hilbert Transform (analytical signal). */ complex_hilbert(mAnalytic); /* Windowing and add to output accumulator */ - for(k = 0;k < HIL_SIZE;k++) - mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k]; - - /* Shift accumulator, input & output FIFO */ - for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k]; - for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k]; - for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero; - for(k = 0;k < FIFO_LATENCY;k++) - mInFIFO[k] = mInFIFO[k+HIL_STEP]; + for(size_t dst{mPos}, k{0u};dst < HilSize;++dst,++k) + mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k]; + for(size_t dst{0u}, k{HilSize-mPos};dst < mPos;++dst,++k) + mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k]; + + /* Copy out the accumulated result, then clear for the next iteration. */ + std::copy_n(mOutputAccum.cbegin() + mPos, HilStep, mOutFIFO.begin()); + std::fill_n(mOutputAccum.begin() + mPos, HilStep, complex_d{}); } /* Process frequency shifter using the analytic signal obtained. */ - for(ALsizei c{0};c < 2;++c) + float *RESTRICT BufferOut{al::assume_aligned<16>(mBufferOut.data())}; + for(size_t c{0};c < 2;++c) { - for(k = 0;k < samplesToDo;++k) + const uint phase_step{mPhaseStep[c]}; + uint phase_idx{mPhase[c]}; + for(size_t k{0};k < samplesToDo;++k) { - double phase = mPhase[c] * ((1.0 / FRACTIONONE) * al::MathDefs<double>::Tau()); + const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) + mOutdata[k].imag()*std::sin(phase)*mSign[c]); - mPhase[c] += mPhaseStep[c]; - mPhase[c] &= FRACTIONMASK; + phase_idx += phase_step; + phase_idx &= MixerFracMask; } + mPhase[c] = phase_idx; /* Now, mix the processed sound data to the output. */ MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, @@ -229,100 +241,11 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float } -void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range"); - props->Fshifter.Frequency = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param); - } -} -void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Fshifter_setParamf(props, context, param, vals[0]); } - -void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range"); - props->Fshifter.LeftDirection = val; - break; - - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range"); - props->Fshifter.RightDirection = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", - param); - } -} -void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Fshifter_setParami(props, context, param, vals[0]); } - -void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - *val = props->Fshifter.LeftDirection; - break; - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - *val = props->Fshifter.RightDirection; - break; - default: - context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", - param); - } -} -void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Fshifter_getParami(props, context, param, vals); } - -void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props->Fshifter.Frequency; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param); - } -} -void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Fshifter_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Fshifter); - - struct FshifterStateFactory final : public EffectStateFactory { - EffectState *create() override { return new FshifterState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new FshifterState{}}; } }; -EffectProps FshifterStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; - props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION; - props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION; - return props; -} - } // namespace EffectStateFactory *FshifterStateFactory_getFactory() diff --git a/alc/effects/modulator.cpp b/alc/effects/modulator.cpp index aee896fb..14ee5004 100644 --- a/alc/effects/modulator.cpp +++ b/alc/effects/modulator.cpp @@ -20,44 +20,53 @@ #include "config.h" -#include <cmath> -#include <cstdlib> - -#include <cmath> #include <algorithm> - -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" +#include <array> +#include <cstdlib> +#include <iterator> + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 128 #define WAVEFORM_FRACBITS 24 #define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS) #define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1) -inline float Sin(ALuint index) +inline float Sin(uint index) { - constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE}; return std::sin(static_cast<float>(index) * scale); } -inline float Saw(ALuint index) +inline float Saw(uint index) { return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; } -inline float Square(ALuint index) +inline float Square(uint index) { return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); } -inline float One(ALuint) { return 1.0f; } +inline float One(uint) { return 1.0f; } -template<float (&func)(ALuint)> -void Modulate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo) +template<float (&func)(uint)> +void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo) { for(size_t i{0u};i < todo;i++) { @@ -69,90 +78,99 @@ void Modulate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo) struct ModulatorState final : public EffectState { - void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){}; + void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; - ALuint mIndex{0}; - ALuint mStep{1}; + uint mIndex{0}; + uint mStep{1}; struct { - BiquadFilter Filter; + uint mTargetChannel{InvalidChannelIndex}; + + BiquadFilter mFilter; - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; + float mCurrentGain{}; + float mTargetGain{}; + } mChans[MaxAmbiChannels]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(ModulatorState) }; -ALboolean ModulatorState::deviceUpdate(const ALCdevice*) +void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - e.Filter.clear(); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = InvalidChannelIndex; + e.mFilter.clear(); + e.mCurrentGain = 0.0f; } - return AL_TRUE; } -void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; - const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1})); + const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)}; + mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); if(mStep == 0) mGetSamples = Modulate<One>; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID) + else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid) mGetSamples = Modulate<Sin>; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH) + else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth) mGetSamples = Modulate<Saw>; - else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/ + else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/ mGetSamples = Modulate<Square>; - ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)}; + float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)}; f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ - mChans[0].Filter.setParams(BiquadType::HighPass, 1.0f, f0norm, - BiquadFilter::rcpQFromBandwidth(f0norm, 0.75f)); + mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) - mChans[i].Filter.copyParamsFrom(mChans[0].Filter); + mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter); mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + auto set_channel = [this](size_t idx, uint outchan, float outgain) { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { for(size_t base{0u};base < samplesToDo;) { - alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES]; - size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + alignas(16) float modsamples[MAX_UPDATE_SAMPLES]; + const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; mGetSamples(modsamples, mIndex, mStep, td); - mIndex += static_cast<ALuint>(mStep * td); + mIndex += static_cast<uint>(mStep * td); mIndex &= WAVEFORM_FRACMASK; - auto chandata = std::addressof(mChans[0]); + auto chandata = std::begin(mChans); for(const auto &input : samplesIn) { - alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES]; - - chandata->Filter.process(temps, &input[base], td); - for(size_t i{0u};i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); + const size_t outidx{chandata->mTargetChannel}; + if(outidx != InvalidChannelIndex) + { + alignas(16) float temps[MAX_UPDATE_SAMPLES]; + + chandata->mFilter.process({&input[base], td}, temps); + for(size_t i{0u};i < td;i++) + temps[i] *= modsamples[i]; + + MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo-base); + } ++chandata; } @@ -161,106 +179,11 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa } -void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range"); - props->Modulator.Frequency = val; - break; - - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range"); - props->Modulator.HighPassCutoff = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Modulator_setParamf(props, context, param, vals[0]); } -void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, context, param, static_cast<ALfloat>(val)); - break; - - case AL_RING_MODULATOR_WAVEFORM: - if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform"); - props->Modulator.Waveform = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Modulator_setParami(props, context, param, vals[0]); } - -void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast<ALint>(props->Modulator.Frequency); - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = static_cast<ALint>(props->Modulator.HighPassCutoff); - break; - case AL_RING_MODULATOR_WAVEFORM: - *val = props->Modulator.Waveform; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Modulator_getParami(props, context, param, vals); } -void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = props->Modulator.Frequency; - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = props->Modulator.HighPassCutoff; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Modulator_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Modulator); - - struct ModulatorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ModulatorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ModulatorState{}}; } }; -EffectProps ModulatorStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; - props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; - props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM; - return props; -} - } // namespace EffectStateFactory *ModulatorStateFactory_getFactory() diff --git a/alc/effects/null.cpp b/alc/effects/null.cpp index e0497296..1f9ae67b 100644 --- a/alc/effects/null.cpp +++ b/alc/effects/null.cpp @@ -1,15 +1,17 @@ #include "config.h" -#include "AL/al.h" -#include "AL/alc.h" +#include <stddef.h> -#include "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" #include "almalloc.h" #include "alspan.h" -#include "effects/base.h" +#include "base.h" +#include "core/bufferline.h" +#include "intrusive_ptr.h" + +struct ContextBase; +struct DeviceBase; +struct EffectSlot; namespace { @@ -18,9 +20,11 @@ struct NullState final : public EffectState { NullState(); ~NullState() override; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(NullState) }; @@ -40,15 +44,14 @@ NullState::~NullState() = default; * format) have been changed. Will always be followed by a call to the update * method, if successful. */ -ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/) +void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/) { - return AL_TRUE; } /* 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 ALeffectslot* /*slot*/, +void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/, const EffectProps* /*props*/, const EffectTarget /*target*/) { } @@ -64,97 +67,13 @@ void NullState::process(const size_t/*samplesToDo*/, } -void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/) -{ - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ - switch(param) - { - default: - NullEffect_setParami(props, context, param, vals[0]); - } -} -void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/) -{ - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) - { - default: - NullEffect_setParamf(props, context, param, vals[0]); - } -} - -void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/) -{ - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ - switch(param) - { - default: - NullEffect_getParami(props, context, param, vals); - } -} -void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/) -{ - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ - switch(param) - { - default: - NullEffect_getParamf(props, context, param, vals); - } -} - -DEFINE_ALEFFECT_VTABLE(NullEffect); - - struct NullStateFactory final : public EffectStateFactory { - EffectState *create() override; - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override; + al::intrusive_ptr<EffectState> create() override; }; /* Creates EffectState objects of the appropriate type. */ -EffectState *NullStateFactory::create() -{ return new NullState{}; } - -/* Returns an ALeffectProps initialized with this effect type's default - * property values. - */ -EffectProps NullStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - return props; -} - -/* Returns a pointer to this effect type's global set/get vtable. */ -const EffectVtable *NullStateFactory::getEffectVtable() const noexcept -{ return &NullEffect_vtable; } +al::intrusive_ptr<EffectState> NullStateFactory::create() +{ return al::intrusive_ptr<EffectState>{new NullState{}}; } } // namespace diff --git a/alc/effects/pshifter.cpp b/alc/effects/pshifter.cpp index d7ba072e..426a2264 100644 --- a/alc/effects/pshifter.cpp +++ b/alc/effects/pshifter.cpp @@ -20,350 +20,284 @@ #include "config.h" -#ifdef HAVE_SSE_INTRINSICS -#include <emmintrin.h> -#endif - -#include <cmath> -#include <cstdlib> +#include <algorithm> #include <array> +#include <cmath> #include <complex> -#include <algorithm> +#include <cstdlib> +#include <iterator> -#include "al/auxeffectslot.h" -#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 "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 complex_d = std::complex<double>; +using uint = unsigned int; +using complex_f = std::complex<float>; -#define STFT_SIZE 1024 -#define STFT_HALF_SIZE (STFT_SIZE>>1) -#define OVERSAMP (1<<2) +constexpr size_t StftSize{1024}; +constexpr size_t StftHalfSize{StftSize >> 1}; +constexpr size_t OversampleFactor{8}; -#define STFT_STEP (STFT_SIZE / OVERSAMP) -#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1)) +static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); +constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ -/* Making this constexpr seems to require C++14. */ -std::array<ALdouble,STFT_SIZE> InitHannWindow() -{ - std::array<ALdouble,STFT_SIZE> ret; - /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ - for(size_t i{0};i < STFT_SIZE>>1;i++) +struct Windower { + alignas(16) std::array<float,StftSize> mData; + + Windower() { - constexpr double scale{al::MathDefs<double>::Pi() / double{STFT_SIZE-1}}; - const double val{std::sin(static_cast<double>(i) * scale)}; - ret[i] = ret[STFT_SIZE-1-i] = val * val; + /* Create lookup table of the Hann window for the desired size. */ + for(size_t i{0};i < StftHalfSize;i++) + { + constexpr double scale{al::numbers::pi / double{StftSize}}; + const double val{std::sin((static_cast<double>(i)+0.5) * scale)}; + mData[i] = mData[StftSize-1-i] = static_cast<float>(val * val); + } } - return ret; -} -alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow(); - - -struct ALphasor { - ALdouble Amplitude; - ALdouble Phase; }; +const Windower gWindow{}; -struct ALfrequencyDomain { - ALdouble Amplitude; - ALdouble Frequency; -}; - -/* Converts complex to ALphasor */ -inline ALphasor rect2polar(const complex_d &number) -{ - ALphasor polar; - polar.Amplitude = std::abs(number); - polar.Phase = std::arg(number); - return polar; -} - -/* Converts ALphasor to complex */ -inline complex_d polar2rect(const ALphasor &number) -{ return std::polar<double>(number.Amplitude, number.Phase); } +struct FrequencyBin { + float Magnitude; + float FreqBin; +}; struct PshifterState final : public EffectState { /* Effect parameters */ - size_t mCount; - ALuint mPitchShiftI; - ALfloat mPitchShift; - ALfloat mFreqPerBin; + size_t mCount; + size_t mPos; + uint mPitchShiftI; + float mPitchShift; /* Effects buffers */ - ALfloat mInFIFO[STFT_SIZE]; - ALfloat mOutFIFO[STFT_STEP]; - ALdouble mLastPhase[STFT_HALF_SIZE+1]; - ALdouble mSumPhase[STFT_HALF_SIZE+1]; - ALdouble mOutputAccum[STFT_SIZE]; + std::array<float,StftSize> mFIFO; + std::array<float,StftHalfSize+1> mLastPhase; + std::array<float,StftHalfSize+1> mSumPhase; + std::array<float,StftSize> mOutputAccum; - complex_d mFFTbuffer[STFT_SIZE]; + std::array<complex_f,StftSize> mFftBuffer; - ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1]; - ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1]; + std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer; + std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer; - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; + alignas(16) FloatBufferLine mBufferOut; /* Effect gains for each output channel */ - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]; + float mCurrentGains[MaxAmbiChannels]; + float mTargetGains[MaxAmbiChannels]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(PshifterState) }; -ALboolean PshifterState::deviceUpdate(const ALCdevice *device) +void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; - mPitchShiftI = FRACTIONONE; + mCount = 0; + mPos = StftSize - StftStep; + mPitchShiftI = MixerFracOne; mPitchShift = 1.0f; - mFreqPerBin = static_cast<float>(device->Frequency) / float{STFT_SIZE}; - std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f); - std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f); - std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0); - std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0); - std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0); - std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{}); - std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{}); - std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{}); + mFIFO.fill(0.0f); + mLastPhase.fill(0.0f); + mSumPhase.fill(0.0f); + mOutputAccum.fill(0.0f); + mFftBuffer.fill(complex_f{}); + mAnalysisBuffer.fill(FrequencyBin{}); + mSynthesisBuffer.fill(FrequencyBin{}); std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - - return AL_TRUE; } -void PshifterState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void PshifterState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const float pitch{std::pow(2.0f, - static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f - )}; - mPitchShiftI = fastf2u(pitch*FRACTIONONE); - mPitchShift = static_cast<float>(mPitchShiftI) * (1.0f/FRACTIONONE); + const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; + const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)}; + mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2); + mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne}; - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains); + ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains); } -void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) +void PshifterState::process(const size_t samplesToDo, + const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { /* Pitch shifter engine based on the work of Stephan Bernsee. * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ */ - static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP}; - const ALdouble freq_per_bin{mFreqPerBin}; - ALfloat *RESTRICT bufferOut{mBufferOut}; - size_t count{mCount}; + /* Cycle offset per update expected of each frequency bin (bin 0 is none, + * bin 1 is x1, bin 2 is x2, etc). + */ + constexpr float expected_cycles{al::numbers::pi_v<float>*2.0f / OversampleFactor}; - for(size_t i{0u};i < samplesToDo;) + for(size_t base{0u};base < samplesToDo;) { - do { - /* Fill FIFO buffer with samples data */ - mInFIFO[count] = samplesIn[0][i]; - bufferOut[i] = mOutFIFO[count - FIFO_LATENCY]; + const size_t todo{minz(StftStep-mCount, samplesToDo-base)}; - count++; - } while(++i < samplesToDo && count < STFT_SIZE); + /* Retrieve the output samples from the FIFO and fill in the new input + * samples. + */ + auto fifo_iter = mFIFO.begin()+mPos + mCount; + std::copy_n(fifo_iter, todo, mBufferOut.begin()+base); - /* Check whether FIFO buffer is filled */ - if(count < STFT_SIZE) break; - count = FIFO_LATENCY; + std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter); + mCount += todo; + base += todo; - /* Real signal windowing and store in FFTbuffer */ - for(ALuint k{0u};k < STFT_SIZE;k++) - { - mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]); - mFFTbuffer[k].imag(0.0); - } + /* Check whether FIFO buffer is filled with new samples. */ + if(mCount < StftStep) break; + mCount = 0; + mPos = (mPos+StftStep) & (mFIFO.size()-1); - /* ANALYSIS */ - /* Apply FFT to FFTbuffer data */ - complex_fft(mFFTbuffer, -1.0); + /* Time-domain signal windowing, store in FftBuffer, and apply a + * forward FFT to get the frequency-domain signal. + */ + for(size_t src{mPos}, k{0u};src < StftSize;++src,++k) + mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; + for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k) + mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; + forward_fft(al::as_span(mFftBuffer)); /* Analyze the obtained data. Since the real FFT is symmetric, only - * STFT_HALF_SIZE+1 samples are needed. + * StftHalfSize+1 samples are needed. */ - for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++) + for(size_t k{0u};k < StftHalfSize+1;k++) { - /* Compute amplitude and phase */ - ALphasor component{rect2polar(mFFTbuffer[k])}; - - /* Compute phase difference and subtract expected phase difference */ - double tmp{(component.Phase - mLastPhase[k]) - k*expected}; - - /* Map delta phase into +/- Pi interval */ - int qpd{double2int(tmp / al::MathDefs<double>::Pi())}; - tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2)); - - /* Get deviation from bin frequency from the +/- Pi interval */ - tmp /= expected; - - /* Compute the k-th partials' true frequency, twice the amplitude - * for maintain the gain (because half of bins are used) and store - * amplitude and true frequency in analysis buffer. + const float magnitude{std::abs(mFftBuffer[k])}; + const float phase{std::arg(mFftBuffer[k])}; + + /* Compute the phase difference from the last update and subtract + * the expected phase difference for this bin. + * + * When oversampling, the expected per-update offset increments by + * 1/OversampleFactor for every frequency bin. So, the offset wraps + * every 'OversampleFactor' bin. */ - mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude; - mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin; + const auto bin_offset = static_cast<float>(k % OversampleFactor); + float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles}; + /* Store the actual phase for the next update. */ + mLastPhase[k] = phase; + + /* Normalize from pi, and wrap the delta between -1 and +1. */ + tmp *= al::numbers::inv_pi_v<float>; + int qpd{float2int(tmp)}; + tmp -= static_cast<float>(qpd + (qpd%2)); + + /* Get deviation from bin frequency (-0.5 to +0.5), and account for + * oversampling. + */ + tmp *= 0.5f * OversampleFactor; - /* Store actual phase[k] for the calculations in the next frame*/ - mLastPhase[k] = component.Phase; + /* Compute the k-th partials' frequency bin target and store the + * magnitude and frequency bin in the analysis buffer. We don't + * need the "true frequency" since it's a linear relationship with + * the bin. + */ + mAnalysisBuffer[k].Magnitude = magnitude; + mAnalysisBuffer[k].FreqBin = static_cast<float>(k) + tmp; } - /* PROCESSING */ - /* pitch shifting */ - for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++) - { - mSyntesis_buffer[k].Amplitude = 0.0; - mSyntesis_buffer[k].Frequency = 0.0; - } + /* Shift the frequency bins according to the pitch adjustment, + * accumulating the magnitudes of overlapping frequency bins. + */ + std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); - for(size_t k{0u};k < STFT_HALF_SIZE+1;k++) + constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1}; + const size_t bin_count{minz(StftHalfSize+1, bin_limit/mPitchShiftI + 1)}; + for(size_t k{0u};k < bin_count;k++) { - size_t j{(k*mPitchShiftI) >> FRACTIONBITS}; - if(j >= STFT_HALF_SIZE+1) break; + const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits}; - mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude; - mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift; + /* If more than two bins end up together, use the target frequency + * bin for the one with the dominant magnitude. There might be a + * better way to handle this, but it's better than last-index-wins. + */ + if(mAnalysisBuffer[k].Magnitude > mSynthesisBuffer[j].Magnitude) + mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift; + mSynthesisBuffer[j].Magnitude += mAnalysisBuffer[k].Magnitude; } - /* SYNTHESIS */ - /* Synthesis the processing data */ - for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++) + /* Reconstruct the frequency-domain signal from the adjusted frequency + * bins. + */ + for(size_t k{0u};k < StftHalfSize+1;k++) { - ALphasor component; - ALdouble tmp; - - /* Compute bin deviation from scaled freq */ - tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k; - - /* Calculate actual delta phase and accumulate it to get bin phase */ - mSumPhase[k] += (k + tmp) * expected; + /* Calculate the actual delta phase for this bin's target frequency + * bin, and accumulate it to get the actual bin phase. + */ + float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles}; - component.Amplitude = mSyntesis_buffer[k].Amplitude; - component.Phase = mSumPhase[k]; + /* Wrap between -pi and +pi for the sum. If mSumPhase is left to + * grow indefinitely, it will lose precision and produce less exact + * phase over time. + */ + tmp *= al::numbers::inv_pi_v<float>; + int qpd{float2int(tmp)}; + tmp -= static_cast<float>(qpd + (qpd%2)); + mSumPhase[k] = tmp * al::numbers::pi_v<float>; - /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/ - mFFTbuffer[k] = polar2rect(component); + mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]); } - /* zero negative frequencies for recontruct a real signal */ - for(ALuint k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++) - mFFTbuffer[k] = complex_d{}; - - /* Apply iFFT to buffer data */ - complex_fft(mFFTbuffer, 1.0); - - /* Windowing and add to output */ - for(ALuint k{0u};k < STFT_SIZE;k++) - mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() / - (0.5 * STFT_HALF_SIZE * OVERSAMP); - - /* Shift accumulator, input & output FIFO */ - size_t j, k; - for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]); - for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k]; - for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0; - for(k = 0;k < FIFO_LATENCY;k++) - mInFIFO[k] = mInFIFO[k+STFT_STEP]; - } - mCount = count; - - /* Now, mix the processed sound data to the output. */ - MixSamples({bufferOut, samplesToDo}, samplesOut, mCurrentGains, mTargetGains, - maxz(samplesToDo, 512), 0); -} + for(size_t k{StftHalfSize+1};k < StftSize;++k) + mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]); + /* Apply an inverse FFT to get the time-domain signal, and accumulate + * for the output with windowing. + */ + inverse_fft(al::as_span(mFftBuffer)); -void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); } + static constexpr float scale{3.0f / OversampleFactor / StftSize}; + for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k) + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; -void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range"); - props->Pshifter.CoarseTune = val; - break; - - case AL_PITCH_SHIFTER_FINE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range"); - props->Pshifter.FineTune = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - param); + /* Copy out the accumulated result, then clear for the next iteration. */ + std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); + std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f); } -} -void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Pshifter_setParami(props, context, param, vals[0]); } -void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - param); - } + /* Now, mix the processed sound data to the output. */ + MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, + maxz(samplesToDo, 512), 0); } -void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Pshifter_getParami(props, context, param, vals); } - -void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Pshifter); struct PshifterStateFactory final : public EffectStateFactory { - EffectState *create() override; - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new PshifterState{}}; } }; -EffectState *PshifterStateFactory::create() -{ return new PshifterState{}; } - -EffectProps PshifterStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; - props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; - return props; -} - } // namespace EffectStateFactory *PshifterStateFactory_getFactory() diff --git a/alc/effects/reverb.cpp b/alc/effects/reverb.cpp index 6e56adf2..3875bedb 100644 --- a/alc/effects/reverb.cpp +++ b/alc/effects/reverb.cpp @@ -20,32 +20,84 @@ #include "config.h" -#include <cstdio> -#include <cstdlib> -#include <cmath> - -#include <array> -#include <numeric> #include <algorithm> +#include <array> +#include <cstdio> #include <functional> - -#include "al/auxeffectslot.h" -#include "al/listener.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "bformatdec.h" -#include "filters/biquad.h" -#include "vector.h" +#include <iterator> +#include <numeric> +#include <stdint.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 "core/filters/splitter.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "vecmat.h" +#include "vector.h" /* This is a user config option for modifying the overall output of the reverb * effect. */ -ALfloat ReverbBoost = 1.0f; +float ReverbBoost = 1.0f; namespace { +using uint = unsigned int; + +constexpr float MaxModulationTime{4.0f}; +constexpr float DefaultModulationTime{0.25f}; + +#define MOD_FRACBITS 24 +#define MOD_FRACONE (1<<MOD_FRACBITS) +#define MOD_FRACMASK (MOD_FRACONE-1) + + +struct CubicFilter { + static constexpr size_t sTableBits{8}; + static constexpr size_t sTableSteps{1 << sTableBits}; + static constexpr size_t sTableMask{sTableSteps - 1}; + + float mFilter[sTableSteps*2 + 1]{}; + + constexpr CubicFilter() + { + /* This creates a lookup table for a cubic spline filter, with 256 + * steps between samples. Only half the coefficients are needed, since + * Coeff2 is just Coeff1 in reverse and Coeff3 is just Coeff0 in + * reverse. + */ + for(size_t i{0};i < sTableSteps;++i) + { + const double mu{static_cast<double>(i) / double{sTableSteps}}; + const double mu2{mu*mu}, mu3{mu2*mu}; + const double a0{-0.5*mu3 + mu2 + -0.5*mu}; + const double a1{ 1.5*mu3 + -2.5*mu2 + 1.0f}; + mFilter[i] = static_cast<float>(a1); + mFilter[sTableSteps+i] = static_cast<float>(a0); + } + } + + constexpr float getCoeff0(size_t i) const noexcept { return mFilter[sTableSteps+i]; } + constexpr float getCoeff1(size_t i) const noexcept { return mFilter[i]; } + constexpr float getCoeff2(size_t i) const noexcept { return mFilter[sTableSteps-i]; } + constexpr float getCoeff3(size_t i) const noexcept { return mFilter[sTableSteps*2-i]; } +}; +constexpr CubicFilter gCubicTable; + + using namespace std::placeholders; /* Max samples per process iteration. Used to limit the size needed for @@ -61,6 +113,15 @@ constexpr size_t MAX_UPDATE_SAMPLES{256}; constexpr size_t NUM_LINES{4u}; +/* This coefficient is used to define the maximum frequency range controlled by + * the modulation depth. The current value of 0.05 will allow it to swing from + * 0.95x to 1.05x. This value must be below 1. At 1 it will cause the sampler + * to stall on the downswing, and above 1 it will cause it to sample backwards. + * The value 0.05 seems be nearest to Creative hardware behavior. + */ +constexpr float MODULATION_DEPTH_COEFF{0.05f}; + + /* The B-Format to A-Format conversion matrix. The arrangement of rows is * deliberately chosen to align the resulting lines to their spatial opposites * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below @@ -68,21 +129,29 @@ constexpr size_t NUM_LINES{4u}; * tetrahedron, but it's close enough. Should the model be extended to 8-lines * in the future, true opposites can be used. */ -alignas(16) constexpr ALfloat B2A[NUM_LINES][MAX_AMBI_CHANNELS]{ - { 0.288675134595f, 0.288675134595f, 0.288675134595f, 0.288675134595f }, - { 0.288675134595f, -0.288675134595f, -0.288675134595f, 0.288675134595f }, - { 0.288675134595f, 0.288675134595f, -0.288675134595f, -0.288675134595f }, - { 0.288675134595f, -0.288675134595f, 0.288675134595f, -0.288675134595f } +alignas(16) constexpr float B2A[NUM_LINES][NUM_LINES]{ + { 0.5f, 0.5f, 0.5f, 0.5f }, + { 0.5f, -0.5f, -0.5f, 0.5f }, + { 0.5f, 0.5f, -0.5f, -0.5f }, + { 0.5f, -0.5f, 0.5f, -0.5f } }; -/* Converts A-Format to B-Format. */ -alignas(16) constexpr ALfloat A2B[NUM_LINES][NUM_LINES]{ - { 0.866025403785f, 0.866025403785f, 0.866025403785f, 0.866025403785f }, - { 0.866025403785f, -0.866025403785f, 0.866025403785f, -0.866025403785f }, - { 0.866025403785f, -0.866025403785f, -0.866025403785f, 0.866025403785f }, - { 0.866025403785f, 0.866025403785f, -0.866025403785f, -0.866025403785f } -}; +/* Converts A-Format to B-Format for early reflections. */ +alignas(16) constexpr std::array<std::array<float,NUM_LINES>,NUM_LINES> EarlyA2B{{ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, + {{ 0.5f, -0.5f, 0.5f, -0.5f }}, + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, + {{ 0.5f, 0.5f, -0.5f, -0.5f }} +}}; +/* Converts A-Format to B-Format for late reverb. */ +constexpr auto InvSqrt2 = static_cast<float>(1.0/al::numbers::sqrt2); +alignas(16) constexpr std::array<std::array<float,NUM_LINES>,NUM_LINES> LateA2B{{ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, + {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, + {{ 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }}, + {{ 0.5f, 0.5f, -0.5f, -0.5f }} +}}; /* The all-pass and delay lines have a variable length dependent on the * effect's density parameter, which helps alter the perceived environment @@ -99,7 +168,7 @@ alignas(16) constexpr ALfloat A2B[NUM_LINES][NUM_LINES]{ * The density scale below will result in a max line multiplier of 50, for an * effective size range of 5m to 50m. */ -constexpr ALfloat DENSITY_SCALE{125000.0f}; +constexpr float DENSITY_SCALE{125000.0f}; /* All delay line lengths are specified in seconds. * @@ -145,7 +214,7 @@ constexpr ALfloat DENSITY_SCALE{125000.0f}; * * Assuming an average of 1m, we get the following taps: */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_TAP_LENGTHS{{ +constexpr std::array<float,NUM_LINES> EARLY_TAP_LENGTHS{{ 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f }}; @@ -155,7 +224,7 @@ constexpr std::array<ALfloat,NUM_LINES> EARLY_TAP_LENGTHS{{ * * Where a is the approximate maximum all-pass cycle limit (20). */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_ALLPASS_LENGTHS{{ +constexpr std::array<float,NUM_LINES> EARLY_ALLPASS_LENGTHS{{ 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f }}; @@ -181,7 +250,7 @@ constexpr std::array<ALfloat,NUM_LINES> EARLY_ALLPASS_LENGTHS{{ * * Using an average dimension of 1m, we get: */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_LINE_LENGTHS{{ +constexpr std::array<float,NUM_LINES> EARLY_LINE_LENGTHS{{ 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f }}; @@ -189,7 +258,7 @@ constexpr std::array<ALfloat,NUM_LINES> EARLY_LINE_LENGTHS{{ * * A_i = (5 / 3) L_i / r_1 */ -constexpr std::array<ALfloat,NUM_LINES> LATE_ALLPASS_LENGTHS{{ +constexpr std::array<float,NUM_LINES> LATE_ALLPASS_LENGTHS{{ 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f }}; @@ -208,7 +277,7 @@ constexpr std::array<ALfloat,NUM_LINES> LATE_ALLPASS_LENGTHS{{ * * For our 1m average room, we get: */ -constexpr std::array<ALfloat,NUM_LINES> LATE_LINE_LENGTHS{{ +constexpr std::array<float,NUM_LINES> LATE_LINE_LENGTHS{{ 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f }}; @@ -232,13 +301,13 @@ struct DelayLineI { { Line = sampleBuffer + LineOffset; } /* Calculate the length of a delay line and store its mask and offset. */ - ALuint calcLineLength(const ALfloat length, const uintptr_t offset, const ALfloat frequency, - const ALuint extra) + uint calcLineLength(const float length, const uintptr_t offset, const float frequency, + const uint extra) { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. */ - ALuint samples{float2uint(std::ceil(length*frequency))}; + uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); /* All lines share a single sample buffer. */ @@ -249,7 +318,7 @@ struct DelayLineI { return samples; } - void write(size_t offset, const size_t c, const ALfloat *RESTRICT in, const size_t count) const noexcept + void write(size_t offset, const size_t c, const float *RESTRICT in, const size_t count) const noexcept { ASSUME(count > 0); for(size_t i{0u};i < count;) @@ -265,32 +334,28 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; - ALfloat Coeff{0.0f}; - size_t Offset[NUM_LINES][2]{}; - - void processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep, - const size_t todo); - void processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, - const ALfloat xCoeff, const ALfloat yCoeff, const size_t todo); + float Coeff{0.0f}; + size_t Offset[NUM_LINES]{}; + + void process(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, + const float xCoeff, const float yCoeff, const size_t todo); }; struct T60Filter { /* Two filters are used to adjust the signal. One to control the low * frequencies, and one to control the high frequencies. */ - ALfloat MidGain[2]{0.0f, 0.0f}; + float MidGain{0.0f}; BiquadFilter HFFilter, LFFilter; - void calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, const ALfloat mfDecayTime, - const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm); + void calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime, + const float hfDecayTime, const float lf0norm, const float hf0norm); /* Applies the two T60 damping filter sections. */ - void process(ALfloat *samples, const size_t todo) - { - HFFilter.process(samples, samples, todo); - LFFilter.process(samples, samples, todo); - } + void process(const al::span<float> samples) + { DualBiquad{HFFilter, LFFilter}.process(samples, samples.data()); } + + void clear() noexcept { HFFilter.clear(); LFFilter.clear(); } }; struct EarlyReflections { @@ -303,61 +368,68 @@ struct EarlyReflections { * reflections. */ DelayLineI Delay; - size_t Offset[NUM_LINES][2]{}; - ALfloat Coeff[NUM_LINES][2]{}; + size_t Offset[NUM_LINES]{}; + float Coeff[NUM_LINES]{}; /* The gain for each output channel based on 3D panning. */ - ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; + float TargetGains[NUM_LINES][MaxAmbiChannels]{}; - void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime, - const ALfloat frequency); + void updateLines(const float density_mult, const float diffusion, const float decayTime, + const float frequency); +}; + + +struct Modulation { + /* The vibrato time is tracked with an index over a (MOD_FRACONE) + * normalized range. + */ + uint Index, Step; + + /* The depth of frequency change, in samples. */ + float Depth; + + float ModDelays[MAX_UPDATE_SAMPLES]; + + void updateModulator(float modTime, float modDepth, float frequency); + + void calcDelays(size_t todo); }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ DelayLineI Delay; - size_t Offset[NUM_LINES][2]{}; + size_t Offset[NUM_LINES]{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. */ - ALfloat DensityGain[2]{0.0f, 0.0f}; + float DensityGain{0.0f}; /* T60 decay filters are used to simulate absorption. */ T60Filter T60[NUM_LINES]; + Modulation Mod; + /* A Gerzon vector all-pass filter is used to simulate diffusion. */ VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ - ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; + float TargetGains[NUM_LINES][MaxAmbiChannels]{}; - void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime, - const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, - const ALfloat hf0norm, const ALfloat frequency); -}; - -struct ReverbState final : public EffectState { - /* All delay lines are allocated as a single buffer to reduce memory - * fragmentation and management code. - */ - al::vector<std::array<float,NUM_LINES>,16> mSampleBuffer; + void updateLines(const float density_mult, const float diffusion, const float lfDecayTime, + const float mfDecayTime, const float hfDecayTime, const float lf0norm, + const float hf0norm, const float frequency); - struct { - /* Calculated parameters which indicate if cross-fading is needed after - * an update. - */ - ALfloat Density{AL_EAXREVERB_DEFAULT_DENSITY}; - ALfloat Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION}; - ALfloat DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE}; - ALfloat LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE}; - } mParams; + void clear() noexcept + { + for(auto &filter : T60) + filter.clear(); + } +}; +struct ReverbPipeline { /* Master effect filters */ struct { BiquadFilter Lp; @@ -365,28 +437,88 @@ struct ReverbState final : public EffectState { } mFilter[NUM_LINES]; /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mDelay; + DelayLineI mEarlyDelayIn; + DelayLineI mLateDelayIn; /* Tap points for early reflection delay. */ - size_t mEarlyDelayTap[NUM_LINES][2]{}; - ALfloat mEarlyDelayCoeff[NUM_LINES][2]{}; + size_t mEarlyDelayTap[NUM_LINES][2]{}; + float mEarlyDelayCoeff[NUM_LINES]{}; /* Tap points for late reverb feed and delay. */ - size_t mLateFeedTap{}; size_t mLateDelayTap[NUM_LINES][2]{}; /* Coefficients for the all-pass and line scattering matrices. */ - ALfloat mMixX{0.0f}; - ALfloat mMixY{0.0f}; + float mMixX{0.0f}; + float mMixY{0.0f}; EarlyReflections mEarly; LateReverb mLate; - bool mDoFading{}; + std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter; + + size_t mFadeSampleCount{1}; + + void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult, + const float decayTime, const float frequency); + void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, + const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); - /* Maximum number of samples to process at once. */ - size_t mMaxUpdate[2]{MAX_UPDATE_SAMPLES, MAX_UPDATE_SAMPLES}; + void processEarly(size_t offset, const size_t samplesToDo, + const al::span<ReverbUpdateLine,NUM_LINES> tempSamples, + const al::span<FloatBufferLine,NUM_LINES> outSamples); + void processLate(size_t offset, const size_t samplesToDo, + const al::span<ReverbUpdateLine,NUM_LINES> tempSamples, + const al::span<FloatBufferLine,NUM_LINES> outSamples); + + void clear() noexcept + { + for(auto &filter : mFilter) + { + filter.Lp.clear(); + filter.Hp.clear(); + } + mLate.clear(); + for(auto &filters : mAmbiSplitter) + { + for(auto &filter : filters) + filter.clear(); + } + } +}; + +struct ReverbState final : public EffectState { + /* All delay lines are allocated as a single buffer to reduce memory + * fragmentation and management code. + */ + al::vector<std::array<float,NUM_LINES>,16> mSampleBuffer; + + struct { + /* Calculated parameters which indicate if cross-fading is needed after + * an update. + */ + 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; + + enum PipelineState : uint8_t { + DeviceClear, + StartFade, + Fading, + Cleanup, + Normal, + }; + PipelineState mPipelineState{DeviceClear}; + uint8_t mCurrentPipeline{0}; + + ReverbPipeline mPipelines[2]; /* The current write offset for all delay lines. */ size_t mOffset{}; @@ -396,95 +528,105 @@ struct ReverbState final : public EffectState { alignas(16) FloatBufferLine mTempLine{}; alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mTempSamples; }; - alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mEarlySamples{}; - alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mLateSamples{}; + alignas(16) std::array<FloatBufferLine,NUM_LINES> mEarlySamples{}; + alignas(16) std::array<FloatBufferLine,NUM_LINES> mLateSamples{}; - using MixOutT = void (ReverbState::*)(const al::span<FloatBufferLine> samplesOut, - const size_t counter, const size_t offset, const size_t todo); + std::array<float,MaxAmbiOrder+1> mOrderScales{}; - MixOutT mMixOut{&ReverbState::MixOutPlain}; - std::array<ALfloat,MAX_AMBI_ORDER+1> mOrderScales{}; - std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter; + bool mUpmixOutput{false}; - void MixOutPlain(const al::span<FloatBufferLine> samplesOut, const size_t counter, - const size_t offset, const size_t todo) + void MixOutPlain(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut, + const size_t todo) { ASSUME(todo > 0); - /* Convert back to B-Format, and mix the results to output. */ - const al::span<float> tmpspan{mTempLine.data(), todo}; + /* When not upsampling, the panning gains convert to B-Format and pan + * at the same time. + */ for(size_t c{0u};c < NUM_LINES;c++) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mEarlySamples[0].data(), - mEarlySamples[0].size()); - MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, - offset); + const al::span<float> tmpspan{mEarlySamples[c].data(), todo}; + MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], + pipeline.mEarly.TargetGains[c], todo, 0); } for(size_t c{0u};c < NUM_LINES;c++) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mLateSamples[0].data(), - mLateSamples[0].size()); - MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, - offset); + const al::span<float> tmpspan{mLateSamples[c].data(), todo}; + MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], + pipeline.mLate.TargetGains[c], todo, 0); } } - void MixOutAmbiUp(const al::span<FloatBufferLine> samplesOut, const size_t counter, - const size_t offset, const size_t todo) + void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut, + const size_t todo) { ASSUME(todo > 0); - const al::span<float> tmpspan{mTempLine.data(), todo}; + auto DoMixRow = [](const al::span<float> OutBuffer, const al::span<const float,4> Gains, + const float *InSamples, const size_t InStride) + { + std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); + for(const float gain : Gains) + { + const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; + InSamples += InStride; + + if(!(std::fabs(gain) > GainSilenceThreshold)) + continue; + + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), + mix_sample); + } + }; + + /* When upsampling, the B-Format conversion needs to be done separately + * so the proper HF scaling can be applied to each B-Format channel. + * The panning gains then pan and upsample the B-Format channels. + */ + const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; for(size_t c{0u};c < NUM_LINES;c++) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, 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. */ - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[0][c].applyHfScale(tmpspan.data(), hfscale, todo); + const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; + pipeline.mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); - MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], + pipeline.mEarly.TargetGains[c], todo, 0); } for(size_t c{0u};c < NUM_LINES;c++) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mLateSamples[0].data(), - mLateSamples[0].size()); + DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[1][c].applyHfScale(tmpspan.data(), hfscale, todo); + const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; + pipeline.mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); - MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], + pipeline.mLate.TargetGains[c], todo, 0); } } - bool allocLines(const ALfloat frequency); - - void updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density, - const ALfloat decayTime, const ALfloat frequency); - void update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, - const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target); - - void earlyUnfaded(const size_t offset, const size_t todo); - void earlyFaded(const size_t offset, const size_t todo, const ALfloat fade, - const ALfloat fadeStep); + void mixOut(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut, const size_t todo) + { + if(mUpmixOutput) + MixOutAmbiUp(pipeline, samplesOut, todo); + else + MixOutPlain(pipeline, samplesOut, todo); + } - void lateUnfaded(const size_t offset, const size_t todo); - void lateFaded(const size_t offset, const size_t todo, const ALfloat fade, - const ALfloat fadeStep); + void allocLines(const float frequency); - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; DEF_NEWDEL(ReverbState) }; @@ -493,14 +635,13 @@ struct ReverbState final : public EffectState { * Device Update * **************************************/ -inline ALfloat CalcDelayLengthMult(ALfloat density) +inline float CalcDelayLengthMult(float density) { return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); } /* Calculates the delay line metrics and allocates the shared sample buffer - * for all lines given the sample rate (frequency). If an allocation failure - * occurs, it returns AL_FALSE. + * for all lines given the sample rate (frequency). */ -bool ReverbState::allocLines(const ALfloat frequency) +void ReverbState::allocLines(const float frequency) { /* All delay line lengths are calculated to accomodate the full range of * lengths given their respective paramters. @@ -510,122 +651,137 @@ bool ReverbState::allocLines(const ALfloat frequency) /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ - ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; + const float multiplier{CalcDelayLengthMult(1.0f)}; - /* The main delay length includes the maximum early reflection delay, the - * largest early tap width, the maximum late reverb delay, and the - * largest late tap width. Finally, it must also be extended by the - * update size (BUFFERSIZE) for block processing. + /* The modulator's line length is calculated from the maximum modulation + * time and depth coefficient, and halfed for the low-to-high frequency + * swing. */ - ALfloat length{AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier + - AL_EAXREVERB_MAX_LATE_REVERB_DELAY + - (LATE_LINE_LENGTHS.back() - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*multiplier}; - totalSamples += mDelay.calcLineLength(length, totalSamples, frequency, BUFFERSIZE); - - /* The early vector all-pass line. */ - length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); - - /* The early reflection line. */ - length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += mEarly.Delay.calcLineLength(length, totalSamples, frequency, 0); - - /* The late vector all-pass line. */ - length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); + constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; - /* The late delay lines are calculated from the largest maximum density - * line length. - */ - length = LATE_LINE_LENGTHS.back() * multiplier; - totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 0); - - if(totalSamples != mSampleBuffer.size()) + for(auto &pipeline : mPipelines) { - mSampleBuffer.resize(totalSamples); - mSampleBuffer.shrink_to_fit(); + /* The main delay length includes the maximum early reflection delay, + * the largest early tap width, the maximum late reverb delay, and the + * largest late tap width. Finally, it must also be extended by the + * update size (BufferLineSize) for block processing. + */ + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; + totalSamples += pipeline.mEarlyDelayIn.calcLineLength(length, totalSamples, frequency, + BufferLineSize); + + constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + float{NUM_LINES}}; + length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier; + totalSamples += pipeline.mLateDelayIn.calcLineLength(length, totalSamples, frequency, + BufferLineSize); + + /* The early vector all-pass line. */ + length = EARLY_ALLPASS_LENGTHS.back() * multiplier; + totalSamples += pipeline.mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, + 0); + + /* The early reflection line. */ + length = EARLY_LINE_LENGTHS.back() * multiplier; + totalSamples += pipeline.mEarly.Delay.calcLineLength(length, totalSamples, frequency, + MAX_UPDATE_SAMPLES); + + /* The late vector all-pass line. */ + length = LATE_ALLPASS_LENGTHS.back() * multiplier; + totalSamples += pipeline.mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, + 0); + + /* The late delay lines are calculated from the largest maximum density + * line length, and the maximum modulation delay. Four additional + * samples are needed for resampling the modulator delay. + */ + length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; + totalSamples += pipeline.mLate.Delay.calcLineLength(length, totalSamples, frequency, 4); } + if(totalSamples != mSampleBuffer.size()) + decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); + /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), std::array<float,NUM_LINES>{}); + std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); /* Update all delays to reflect the new sample buffer. */ - mDelay.realizeLineOffset(mSampleBuffer.data()); - mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); - mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - mLate.Delay.realizeLineOffset(mSampleBuffer.data()); - - return true; + for(auto &pipeline : mPipelines) + { + pipeline.mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data()); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data()); + } } -ALboolean ReverbState::deviceUpdate(const ALCdevice *device) +void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { - const auto frequency = static_cast<ALfloat>(device->Frequency); + const auto frequency = static_cast<float>(device->Frequency); /* Allocate the delay lines. */ - if(!allocLines(frequency)) - return AL_FALSE; + allocLines(frequency); - const ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; - - /* 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); - - /* Clear filters and gain coefficients since the delay lines were all just - * cleared (if not reallocated). - */ - for(auto &filter : mFilter) + for(auto &pipeline : mPipelines) { - filter.Lp.clear(); - filter.Hp.clear(); - } + /* Clear filters and gain coefficients since the delay lines were all just + * cleared (if not reallocated). + */ + for(auto &filter : pipeline.mFilter) + { + filter.Lp.clear(); + filter.Hp.clear(); + } - for(auto &coeff : mEarlyDelayCoeff) - std::fill(std::begin(coeff), std::end(coeff), 0.0f); - for(auto &coeff : mEarly.Coeff) - std::fill(std::begin(coeff), std::end(coeff), 0.0f); + std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); + std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); - mLate.DensityGain[0] = 0.0f; - mLate.DensityGain[1] = 0.0f; - for(auto &t60 : mLate.T60) - { - t60.MidGain[0] = 0.0f; - t60.MidGain[1] = 0.0f; - t60.HFFilter.clear(); - t60.LFFilter.clear(); + pipeline.mLate.DensityGain = 0.0f; + for(auto &t60 : pipeline.mLate.T60) + { + t60.MidGain = 0.0f; + t60.HFFilter.clear(); + t60.LFFilter.clear(); + } + + pipeline.mLate.Mod.Index = 0; + pipeline.mLate.Mod.Step = 1; + pipeline.mLate.Mod.Depth = 0.0f; + + for(auto &gains : pipeline.mEarly.CurrentGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mEarly.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mLate.CurrentGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mLate.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); } + mPipelineState = DeviceClear; - for(auto &gains : mEarly.CurrentGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mEarly.PanGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mLate.CurrentGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mLate.PanGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - - /* Reset fading and offset base. */ - mDoFading = true; - std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), MAX_UPDATE_SAMPLES); + /* Reset offset base. */ mOffset = 0; if(device->mAmbiOrder > 1) { - mMixOut = &ReverbState::MixOutAmbiUp; - mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder); + mUpmixOutput = true; + mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder, device->m2DMixing); } else { - mMixOut = &ReverbState::MixOutPlain; + mUpmixOutput = false; mOrderScales.fill(1.0f); } - mAmbiSplitter[0][0].init(400.0f / frequency); - std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]); - std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]); - - return AL_TRUE; + mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); + for(auto &pipeline : mPipelines) + { + std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), + pipeline.mAmbiSplitter[0][0]); + std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), + pipeline.mAmbiSplitter[0][0]); + } } /************************************** @@ -635,19 +791,22 @@ ALboolean ReverbState::deviceUpdate(const ALCdevice *device) /* Calculate a decay coefficient given the length of each cycle and the time * until the decay reaches -60 dB. */ -inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime) -{ return std::pow(REVERB_DECAY_GAIN, length/decayTime); } +inline float CalcDecayCoeff(const float length, const float decayTime) +{ return std::pow(ReverbDecayGain, length/decayTime); } /* Calculate a decay length from a coefficient and the time until the decay * reaches -60 dB. */ -inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime) -{ return std::log10(coeff) * decayTime / std::log10(REVERB_DECAY_GAIN); } +inline float CalcDecayLength(const float coeff, const float decayTime) +{ + constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; + return std::log10(coeff) * decayTime / log10_decaygain; +} /* Calculate an attenuation to be applied to the input of any echo models to * compensate for modal density and decay time. */ -inline ALfloat CalcDensityGain(const ALfloat a) +inline float CalcDensityGain(const float a) { /* The energy of a signal can be obtained by finding the area under the * squared signal. This takes the form of Sum(x_n^2), where x is the @@ -666,11 +825,11 @@ inline ALfloat CalcDensityGain(const ALfloat a) } /* Calculate the scattering matrix coefficients given a diffusion factor. */ -inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y) +inline void CalcMatrixCoeffs(const float diffusion, float *x, float *y) { /* The matrix is of order 4, so n is sqrt(4 - 1). */ - ALfloat n{std::sqrt(3.0f)}; - ALfloat t{diffusion * std::atan(n)}; + constexpr float n{al::numbers::sqrt3_v<float>}; + const float t{diffusion * std::atan(n)}; /* Calculate the first mixing matrix coefficient. */ *x = std::cos(t); @@ -681,19 +840,18 @@ inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y) /* Calculate the limited HF ratio for use with the late reverb low-pass * filters. */ -ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF, - const ALfloat decayTime) +float CalcLimitedHfRatio(const float hfRatio, const float airAbsorptionGainHF, + const float decayTime) { /* Find the attenuation due to air absorption in dB (converting delay * time to meters using the speed of sound). Then reversing the decay * equation, solve for HF ratio. The delay length is cancelled out of * the equation, so it can be calculated once for all lines. */ - ALfloat limitRatio{1.0f / - (CalcDecayLength(airAbsorptionGainHF, decayTime) * SPEEDOFSOUNDMETRESPERSEC)}; + float limitRatio{1.0f / SpeedOfSoundMetersPerSec / + CalcDecayLength(airAbsorptionGainHF, decayTime)}; - /* Using the limit calculated above, apply the upper bound to the HF ratio. - */ + /* Using the limit calculated above, apply the upper bound to the HF ratio. */ return minf(limitRatio, hfRatio); } @@ -702,60 +860,89 @@ ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGai * of specified length, using a combination of two shelf filter sections given * decay times for each band split at two reference frequencies. */ -void T60Filter::calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, - const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, - const ALfloat hf0norm) +void T60Filter::calcCoeffs(const float length, const float lfDecayTime, + const float mfDecayTime, const float hfDecayTime, const float lf0norm, + const float hf0norm) { - const ALfloat mfGain{CalcDecayCoeff(length, mfDecayTime)}; - const ALfloat lfGain{maxf(CalcDecayCoeff(length, lfDecayTime)/mfGain, 0.001f)}; - const ALfloat hfGain{maxf(CalcDecayCoeff(length, hfDecayTime)/mfGain, 0.001f)}; - - MidGain[1] = mfGain; - LFFilter.setParams(BiquadType::LowShelf, lfGain, lf0norm, - LFFilter.rcpQFromSlope(lfGain, 1.0f)); - HFFilter.setParams(BiquadType::HighShelf, hfGain, hf0norm, - HFFilter.rcpQFromSlope(hfGain, 1.0f)); + const float mfGain{CalcDecayCoeff(length, mfDecayTime)}; + const float lfGain{CalcDecayCoeff(length, lfDecayTime) / mfGain}; + const float hfGain{CalcDecayCoeff(length, hfDecayTime) / mfGain}; + + MidGain = mfGain; + LFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f); + HFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f); } /* Update the early reflection line lengths and gain coefficients. */ -void EarlyReflections::updateLines(const ALfloat density, const ALfloat diffusion, - const ALfloat decayTime, const ALfloat frequency) +void EarlyReflections::updateLines(const float density_mult, const float diffusion, + const float decayTime, const float frequency) { - const ALfloat multiplier{CalcDelayLengthMult(density)}; - /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f); + VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { - /* Calculate the length (in seconds) of each all-pass line. */ - ALfloat length{EARLY_ALLPASS_LENGTHS[i] * multiplier}; + /* Calculate the delay length of each all-pass line. */ + float length{EARLY_ALLPASS_LENGTHS[i] * density_mult}; + VecAp.Offset[i] = float2uint(length * frequency); - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2uint(length * frequency); - - /* Calculate the length (in seconds) of each delay line. */ - length = EARLY_LINE_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2uint(length * frequency); + /* Calculate the delay length of each delay line. */ + length = EARLY_LINE_LENGTHS[i] * density_mult; + Offset[i] = float2uint(length * frequency); /* Calculate the gain (coefficient) for each line. */ - Coeff[i][1] = CalcDecayCoeff(length, decayTime); + Coeff[i] = CalcDecayCoeff(length, decayTime); } } +/* Update the EAX modulation step and depth. Keep in mind that this kind of + * vibrato is additive and not multiplicative as one may expect. The downswing + * will sound stronger than the upswing. + */ +void Modulation::updateModulator(float modTime, float modDepth, float frequency) +{ + /* Modulation is calculated in two parts. + * + * The modulation time effects the sinus rate, altering the speed of + * frequency changes. An index is incremented for each sample with an + * appropriate step size to generate an LFO, which will vary the feedback + * delay over time. + */ + Step = maxu(fastf2u(MOD_FRACONE / (frequency * modTime)), 1); + + /* The modulation depth effects the amount of frequency change over the + * range of the sinus. It needs to be scaled by the modulation time so that + * a given depth produces a consistent change in frequency over all ranges + * of time. Since the depth is applied to a sinus value, it needs to be + * halved once for the sinus range and again for the sinus swing in time + * (half of it is spent decreasing the frequency, half is spent increasing + * it). + */ + if(modTime >= DefaultModulationTime) + { + /* To cancel the effects of a long period modulation on the late + * reverberation, the amount of pitch should be varied (decreased) + * according to the modulation time. The natural form is varying + * inversely, in fact resulting in an invariant. + */ + Depth = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; + } + else + Depth = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; +} + /* Update the late reverb line lengths and T60 coefficients. */ -void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, - const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime, - const ALfloat lf0norm, const ALfloat hf0norm, const ALfloat frequency) +void LateReverb::updateLines(const float density_mult, const float diffusion, + const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, + const float lf0norm, const float hf0norm, const float frequency) { /* Scaling factor to convert the normalized reference frequencies from * representing 0...freq to 0...max_reference. */ - const ALfloat norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE}; + constexpr float MaxHFReference{20000.0f}; + const float norm_weight_factor{frequency / MaxHFReference}; - const ALfloat late_allpass_avg{ + const float late_allpass_avg{ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / float{NUM_LINES}}; @@ -767,42 +954,42 @@ void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, * The average length of the delay lines is used to calculate the * attenuation coefficient. */ - const ALfloat multiplier{CalcDelayLengthMult(density)}; - ALfloat length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / - float{NUM_LINES} * multiplier}; - length += late_allpass_avg * multiplier; + float length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / + float{NUM_LINES} + late_allpass_avg}; + length *= density_mult; /* The density gain calculation uses an average decay time weighted by * approximate bandwidth. This attempts to compensate for losses of energy * that reduce decay time due to scattering into highly attenuated bands. */ - const ALfloat decayTimeWeighted{ - (lf0norm*norm_weight_factor)*lfDecayTime + - (hf0norm*norm_weight_factor - lf0norm*norm_weight_factor)*mfDecayTime + + const float decayTimeWeighted{ + lf0norm*norm_weight_factor*lfDecayTime + + (hf0norm - lf0norm)*norm_weight_factor*mfDecayTime + (1.0f - hf0norm*norm_weight_factor)*hfDecayTime}; - DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); + DensityGain = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f); + VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { - /* Calculate the length (in seconds) of each all-pass line. */ - length = LATE_ALLPASS_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2uint(length * frequency); + /* Calculate the delay length of each all-pass line. */ + length = LATE_ALLPASS_LENGTHS[i] * density_mult; + VecAp.Offset[i] = float2uint(length * frequency); - /* Calculate the length (in seconds) of each delay line. */ - length = LATE_LINE_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2uint(length*frequency + 0.5f); + /* Calculate the delay length of each feedback delay line. A cubic + * resampler is used for modulation on the feedback delay, which + * includes one sample of delay. Reduce by one to compensate. + */ + length = LATE_LINE_LENGTHS[i] * density_mult; + Offset[i] = maxu(float2uint(length*frequency + 0.5f), 1u) - 1u; /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 - * filter for each of its four lines. + * filter for each of its four lines. Also include the average + * modulation delay (depth is half the max delay in samples). */ - length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion) * multiplier; + length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + + Mod.Depth/frequency; /* Calculate the T60 damping coefficients for each line. */ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); @@ -811,11 +998,9 @@ void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, /* Update the offsets for the main effect delay line. */ -void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, - const ALfloat density, const ALfloat decayTime, const ALfloat frequency) +void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDelay, + const float density_mult, const float decayTime, const float frequency) { - const ALfloat multiplier{CalcDelayLengthMult(density)}; - /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. * This approximation is linear and so the above density multiplier can @@ -828,15 +1013,13 @@ void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDe */ for(size_t i{0u};i < NUM_LINES;i++) { - ALfloat length{earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier}; - mEarlyDelayTap[i][1] = float2uint(length * frequency); + float length{EARLY_TAP_LENGTHS[i]*density_mult}; + mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); + mEarlyDelayCoeff[i] = CalcDecayCoeff(length, decayTime); - length = EARLY_TAP_LENGTHS[i]*multiplier; - mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime); - - length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*multiplier + + length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult + lateDelay; - mLateDelayTap[i][1] = mLateFeedTap + float2uint(length * frequency); + mLateDelayTap[i][1] = float2uint(length * frequency); } } @@ -845,10 +1028,8 @@ void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDe * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ -alu::Matrix GetTransformFromVector(const ALfloat *vec) +std::array<std::array<float,4>,4> GetTransformFromVector(const float *vec) { - constexpr float sqrt_3{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 @@ -856,13 +1037,13 @@ alu::Matrix GetTransformFromVector(const ALfloat *vec) * rest of OpenAL which use right-handed. This is fixed by negating Z, * which cancels out with the B-Format Z negation. */ - ALfloat norm[3]; - ALfloat mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; + float norm[3]; + float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { - norm[0] = vec[0] / mag * -sqrt_3; - norm[1] = vec[1] / mag * sqrt_3; - norm[2] = vec[2] / mag * sqrt_3; + norm[0] = vec[0] / mag * -al::numbers::sqrt3_v<float>; + norm[1] = vec[1] / mag * al::numbers::sqrt3_v<float>; + norm[2] = vec[2] / mag * al::numbers::sqrt3_v<float>; mag = 1.0f; } else @@ -871,131 +1052,201 @@ alu::Matrix GetTransformFromVector(const ALfloat *vec) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ - norm[0] = vec[0] * -sqrt_3; - norm[1] = vec[1] * sqrt_3; - norm[2] = vec[2] * sqrt_3; + norm[0] = vec[0] * -al::numbers::sqrt3_v<float>; + norm[1] = vec[1] * al::numbers::sqrt3_v<float>; + norm[2] = vec[2] * al::numbers::sqrt3_v<float>; } - return alu::Matrix{ - 1.0f, 0.0f, 0.0f, 0.0f, - norm[0], 1.0f-mag, 0.0f, 0.0f, - norm[1], 0.0f, 1.0f-mag, 0.0f, - norm[2], 0.0f, 0.0f, 1.0f-mag - }; + return std::array<std::array<float,4>,4>{{ + {{1.0f, 0.0f, 0.0f, 0.0f}}, + {{norm[0], 1.0f-mag, 0.0f, 0.0f}}, + {{norm[1], 0.0f, 1.0f-mag, 0.0f}}, + {{norm[2], 0.0f, 0.0f, 1.0f-mag}} + }}; } /* Update the early and late 3D panning gains. */ -void ReverbState::update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, - const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target) +void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, + const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ - const alu::Matrix earlymat{GetTransformFromVector(ReflectionsPan)}; - const alu::Matrix latemat{GetTransformFromVector(LateReverbPan)}; + const std::array<std::array<float,4>,4> earlymat{GetTransformFromVector(ReflectionsPan)}; + const std::array<std::array<float,4>,4> latemat{GetTransformFromVector(LateReverbPan)}; - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < NUM_LINES;i++) - { - const ALfloat coeffs[MAX_AMBI_CHANNELS]{earlymat[0][i], earlymat[1][i], earlymat[2][i], - earlymat[3][i]}; - ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]); - } - for(size_t i{0u};i < NUM_LINES;i++) + if(doUpmix) { - const ALfloat coeffs[MAX_AMBI_CHANNELS]{latemat[0][i], latemat[1][i], latemat[2][i], - latemat[3][i]}; - ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]); - } -} + /* When upsampling, combine the early and late transforms with the + * first-order upsample matrix. This results in panning gains that + * apply the panning transform to first-order B-Format, which is then + * upsampled. + */ + auto mult_matrix = [](const al::span<const std::array<float,4>,4> mtx1) + { + auto&& mtx2 = AmbiScale::FirstOrderUp; + std::array<std::array<float,MaxAmbiChannels>,NUM_LINES> res{}; -void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *Device{Context->mDevice.get()}; - const auto frequency = static_cast<ALfloat>(Device->Frequency); + for(size_t i{0};i < mtx1[0].size();++i) + { + float *RESTRICT dst{res[i].data()}; + for(size_t k{0};k < mtx1.size();++k) + { + const float *RESTRICT src{mtx2[k].data()}; + const float a{mtx1[k][i]}; + for(size_t j{0};j < mtx2[0].size();++j) + dst[j] += a * src[j]; + } + } - /* Calculate the master filters */ - ALfloat hf0norm{minf(props->Reverb.HFReference / frequency, 0.49f)}; - /* Restrict the filter gains from going below -60dB to keep the filter from - * killing most of the signal. - */ - ALfloat gainhf{maxf(props->Reverb.GainHF, 0.001f)}; - mFilter[0].Lp.setParams(BiquadType::HighShelf, gainhf, hf0norm, - mFilter[0].Lp.rcpQFromSlope(gainhf, 1.0f)); - ALfloat lf0norm{minf(props->Reverb.LFReference / frequency, 0.49f)}; - ALfloat gainlf{maxf(props->Reverb.GainLF, 0.001f)}; - mFilter[0].Hp.setParams(BiquadType::LowShelf, gainlf, lf0norm, - mFilter[0].Hp.rcpQFromSlope(gainlf, 1.0f)); - for(size_t i{1u};i < NUM_LINES;i++) - { - mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp); - mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp); + return res; + }; + auto earlycoeffs = mult_matrix(earlymat); + auto latecoeffs = mult_matrix(latemat); + + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); } + else + { + /* When not upsampling, combine the early and late A-to-B-Format + * conversions with their respective transform. This results panning + * gains that convert A-Format to B-Format, which is then panned. + */ + auto mult_matrix = [](const al::span<const std::array<float,NUM_LINES>,4> mtx1, + const al::span<const std::array<float,4>,4> mtx2) + { + std::array<std::array<float,MaxAmbiChannels>,NUM_LINES> res{}; - /* Update the main effect delay and associated taps. */ - updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - props->Reverb.Density, props->Reverb.DecayTime, frequency); + for(size_t i{0};i < mtx1[0].size();++i) + { + float *RESTRICT dst{res[i].data()}; + for(size_t k{0};k < mtx1.size();++k) + { + const float a{mtx1[k][i]}; + for(size_t j{0};j < mtx2.size();++j) + dst[j] += a * mtx2[j][k]; + } + } - /* Update the early lines. */ - mEarly.updateLines(props->Reverb.Density, props->Reverb.Diffusion, props->Reverb.DecayTime, - frequency); + return res; + }; + auto earlycoeffs = mult_matrix(EarlyA2B, earlymat); + auto latecoeffs = mult_matrix(LateA2B, latemat); - /* Get the mixing matrix coefficients. */ - CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY); + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); + } +} + +void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, + const EffectProps *props, const EffectTarget target) +{ + const DeviceBase *Device{Context->mDevice}; + const auto frequency = static_cast<float>(Device->Frequency); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ - ALfloat hfRatio{props->Reverb.DecayHFRatio}; + float hfRatio{props->Reverb.DecayHFRatio}; if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, props->Reverb.DecayTime); /* Calculate the LF/HF decay times. */ - const ALfloat lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio, - AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)}; - const ALfloat hfDecayTime{clampf(props->Reverb.DecayTime * hfRatio, - AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)}; - - /* Update the late lines. */ - mLate.updateLines(props->Reverb.Density, props->Reverb.Diffusion, lfDecayTime, - props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); - - /* Update early and late 3D panning. */ - const ALfloat gain{props->Reverb.Gain * Slot->Params.Gain * ReverbBoost}; - update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, - props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target); - - /* Calculate the max update size from the smallest relevant delay. */ - mMaxUpdate[1] = minz(MAX_UPDATE_SAMPLES, minz(mEarly.Offset[0][1], mLate.Offset[0][1])); - - /* Determine if delay-line cross-fading is required. Density is essentially - * a master control for the feedback delays, so changes the offsets of many - * delay lines. - */ - mDoFading |= (mParams.Density != props->Reverb.Density || + 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)}; + + /* Determine if a full update is required. */ + const bool fullUpdate{mPipelineState == DeviceClear || + /* Density is essentially a master control for the feedback delays, so + * changes the offsets of many delay lines. + */ + mParams.Density != props->Reverb.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ - mParams.Diffusion != props->Reverb.Diffusion || - mParams.DecayTime != props->Reverb.DecayTime || - mParams.HFDecayTime != hfDecayTime || - mParams.LFDecayTime != lfDecayTime || - /* HF/LF References control the weighting used to calculate the density - * gain. - */ - mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference); - if(mDoFading) + mParams.Diffusion != props->Reverb.Diffusion || + mParams.DecayTime != props->Reverb.DecayTime || + mParams.HFDecayTime != hfDecayTime || + mParams.LFDecayTime != lfDecayTime || + /* Modulation time and depth both require fading the modulation delay. */ + mParams.ModulationTime != props->Reverb.ModulationTime || + mParams.ModulationDepth != props->Reverb.ModulationDepth || + /* HF/LF References control the weighting used to calculate the density + * gain. + */ + mParams.HFReference != props->Reverb.HFReference || + mParams.LFReference != props->Reverb.LFReference}; + if(fullUpdate) { mParams.Density = props->Reverb.Density; mParams.Diffusion = props->Reverb.Diffusion; mParams.DecayTime = props->Reverb.DecayTime; mParams.HFDecayTime = hfDecayTime; mParams.LFDecayTime = lfDecayTime; + mParams.ModulationTime = props->Reverb.ModulationTime; + mParams.ModulationDepth = props->Reverb.ModulationDepth; mParams.HFReference = props->Reverb.HFReference; mParams.LFReference = props->Reverb.LFReference; + + mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal; + mCurrentPipeline ^= 1; + } + auto &pipeline = mPipelines[mCurrentPipeline]; + + /* Update early and late 3D panning. */ + mOutTarget = target.Main->Buffer; + const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; + pipeline.update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, + props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, mUpmixOutput, + target.Main); + + /* Calculate the master filters */ + float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); + float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); + for(size_t i{1u};i < NUM_LINES;i++) + { + pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp); + pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp); } + + /* The density-based room size (delay length) multiplier. */ + const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; + + /* Update the main effect delay and associated taps. */ + pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, + density_mult, props->Reverb.DecayTime, frequency); + + if(fullUpdate) + { + /* Update the early lines. */ + pipeline.mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, + frequency); + + /* Get the mixing matrix coefficients. */ + CalcMatrixCoeffs(props->Reverb.Diffusion, &pipeline.mMixX, &pipeline.mMixY); + + /* Update the modulator rate and depth. */ + pipeline.mLate.Mod.updateModulator(props->Reverb.ModulationTime, + props->Reverb.ModulationDepth, frequency); + + /* Update the late lines. */ + pipeline.mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, + props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); + } + + const float decaySamples{(props->Reverb.ReflectionsDelay + props->Reverb.LateReverbDelay + + props->Reverb.DecayTime) * frequency}; + pipeline.mFadeSampleCount = static_cast<size_t>(minf(decaySamples, 1'000'000.0f)); } @@ -1042,19 +1293,19 @@ void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, co * whose combination of signs are being iterated. */ inline auto VectorPartialScatter(const std::array<float,NUM_LINES> &RESTRICT in, - const ALfloat xCoeff, const ALfloat yCoeff) -> std::array<float,NUM_LINES> + const float xCoeff, const float yCoeff) -> std::array<float,NUM_LINES> { - std::array<float,NUM_LINES> out; - out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]); - out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]); - out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]); - out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ); - return out; + return std::array<float,NUM_LINES>{{ + xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), + xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]), + xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]), + xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ) + }}; } /* Utilizes the above, but reverses the input channels. */ -void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const ALfloat xCoeff, - const ALfloat yCoeff, const al::span<const ReverbUpdateLine,NUM_LINES> in, const size_t count) +void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float xCoeff, + const float yCoeff, const al::span<const ReverbUpdateLine,NUM_LINES> in, const size_t count) { ASSUME(count > 0); @@ -1083,17 +1334,17 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const ALfloa * Two static specializations are used for transitional (cross-faded) delay * line processing and non-transitional processing. */ -void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, - const ALfloat xCoeff, const ALfloat yCoeff, const size_t todo) +void VecAllpass::process(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, + const float xCoeff, const float yCoeff, const size_t todo) { const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; + const float feedCoeff{Coeff}; ASSUME(todo > 0); size_t vap_offset[NUM_LINES]; for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] = offset - Offset[j][0]; + vap_offset[j] = offset - Offset[j]; for(size_t i{0u};i < todo;) { for(size_t j{0u};j < NUM_LINES;j++) @@ -1109,60 +1360,8 @@ void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> sampl std::array<float,NUM_LINES> f; for(size_t j{0u};j < NUM_LINES;j++) { - const ALfloat input{samples[j][i]}; - const ALfloat out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; - f[j] = input + feedCoeff*out; - - samples[j][i] = out; - } - ++i; - - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); - } while(--td); - } -} -void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep, - const size_t todo) -{ - const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; - - ASSUME(todo > 0); - - size_t vap_offset[NUM_LINES][2]; - for(size_t j{0u};j < NUM_LINES;j++) - { - vap_offset[j][0] = offset - Offset[j][0]; - vap_offset[j][1] = offset - Offset[j][1]; - } - for(size_t i{0u};i < todo;) - { - for(size_t j{0u};j < NUM_LINES;j++) - { - vap_offset[j][0] &= delay.Mask; - vap_offset[j][1] &= delay.Mask; - } - offset &= delay.Mask; - - size_t maxoff{offset}; - for(size_t j{0u};j < NUM_LINES;j++) - maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1])); - size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; - - do { - fadeCount += 1.0f; - const float fade{fadeCount * fadeStep}; - - std::array<float,NUM_LINES> f; - for(size_t j{0u};j < NUM_LINES;j++) - f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) + - delay.Line[vap_offset[j][1]++][j]*fade; - - for(size_t j{0u};j < NUM_LINES;j++) - { - const ALfloat input{samples[j][i]}; - const ALfloat out{f[j] - feedCoeff*input}; + const float input{samples[j][i]}; + const float out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; @@ -1189,892 +1388,373 @@ void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples * * Finally, the early response is reversed, scattered (based on diffusion), * and fed into the late reverb section of the main delay line. - * - * Two static specializations are used for transitional (cross-faded) delay - * line processing and non-transitional processing. */ -void ReverbState::earlyUnfaded(const size_t offset, const size_t todo) +void ReverbPipeline::processEarly(size_t offset, const size_t samplesToDo, + const al::span<ReverbUpdateLine, NUM_LINES> tempSamples, + const al::span<FloatBufferLine, NUM_LINES> outSamples) { const DelayLineI early_delay{mEarly.Delay}; - const DelayLineI main_delay{mDelay}; - const ALfloat mixX{mMixX}; - const ALfloat mixY{mMixY}; - - ASSUME(todo > 0); + const DelayLineI in_delay{mEarlyDelayIn}; + const float mixX{mMixX}; + const float mixY{mMixY}; - /* First, load decorrelated samples from the main delay line as the primary - * reflections. - */ - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t early_delay_tap{offset - mEarlyDelayTap[j][0]}; - const ALfloat coeff{mEarlyDelayCoeff[j][0]}; - for(size_t i{0u};i < todo;) - { - early_delay_tap &= main_delay.Mask; - size_t td{minz(main_delay.Mask+1 - early_delay_tap, todo - i)}; - do { - mTempSamples[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff; - } while(--td); - } - } - - /* Apply a vector all-pass, to help color the initial reflections based on - * the diffusion strength. - */ - mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); + ASSUME(samplesToDo > 0); - /* Apply a delay and bounce to generate secondary reflections, combine with - * the primary reflections and write out the result for mixing. - */ - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t base{0};base < samplesToDo;) { - size_t feedb_tap{offset - mEarly.Offset[j][0]}; - const ALfloat feedb_coeff{mEarly.Coeff[j][0]}; - float *out = mEarlySamples[j].data(); + const size_t todo{minz(samplesToDo-base, MAX_UPDATE_SAMPLES)}; - for(size_t i{0u};i < todo;) + /* First, load decorrelated samples from the main delay line as the + * primary reflections. + */ + const float fadeStep{1.0f / static_cast<float>(todo)}; + for(size_t j{0u};j < NUM_LINES;j++) { - feedb_tap &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - out[i] = mTempSamples[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff; - ++i; - } while(--td); - } - } - for(size_t j{0u};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); + size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; + size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; + const float coeff{mEarlyDelayCoeff[j]}; + const float coeffStep{early_delay_tap0 != early_delay_tap1 ? coeff*fadeStep : 0.0f}; + float fadeCount{0.0f}; - /* Also write the result back to the main delay line for the late reverb - * stage to pick up at the appropriate time, appplying a scatter and - * bounce to improve the initial diffusion in the late reverb. - */ - const size_t late_feed_tap{offset - mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); -} -void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALfloat fade, - const ALfloat fadeStep) -{ - const DelayLineI early_delay{mEarly.Delay}; - const DelayLineI main_delay{mDelay}; - const ALfloat mixX{mMixX}; - const ALfloat mixY{mMixY}; - - ASSUME(todo > 0); + for(size_t i{0u};i < todo;) + { + early_delay_tap0 &= in_delay.Mask; + early_delay_tap1 &= in_delay.Mask; + const size_t max_tap{maxz(early_delay_tap0, early_delay_tap1)}; + size_t td{minz(in_delay.Mask+1 - max_tap, todo-i)}; + do { + const float fade0{coeff - coeffStep*fadeCount}; + const float fade1{coeffStep*fadeCount}; + fadeCount += 1.0f; + tempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 + + in_delay.Line[early_delay_tap1++][j]*fade1; + } while(--td); + } - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; - size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; - const ALfloat oldCoeff{mEarlyDelayCoeff[j][0]}; - const ALfloat oldCoeffStep{-oldCoeff * fadeStep}; - const ALfloat newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep}; - ALfloat fadeCount{fade}; - - for(size_t i{0u};i < todo;) - { - early_delay_tap0 &= main_delay.Mask; - early_delay_tap1 &= main_delay.Mask; - size_t td{minz(main_delay.Mask+1 - maxz(early_delay_tap0, early_delay_tap1), todo-i)}; - do { - fadeCount += 1.0f; - const ALfloat fade0{oldCoeff + oldCoeffStep*fadeCount}; - const ALfloat fade1{newCoeffStep*fadeCount}; - mTempSamples[j][i++] = - main_delay.Line[early_delay_tap0++][j]*fade0 + - main_delay.Line[early_delay_tap1++][j]*fade1; - } while(--td); + mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; } - } - mEarly.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); + /* Apply a vector all-pass, to help color the initial reflections based + * on the diffusion strength. + */ + mEarly.VecAp.process(tempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t feedb_tap0{offset - mEarly.Offset[j][0]}; - size_t feedb_tap1{offset - mEarly.Offset[j][1]}; - const ALfloat feedb_oldCoeff{mEarly.Coeff[j][0]}; - const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep}; - const ALfloat feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep}; - float *out = mEarlySamples[j].data(); - ALfloat fadeCount{fade}; - - for(size_t i{0u};i < todo;) + /* Apply a delay and bounce to generate secondary reflections, combine + * with the primary reflections and write out the result for mixing. + */ + for(size_t j{0u};j < NUM_LINES;j++) + early_delay.write(offset, NUM_LINES-1-j, tempSamples[j].data(), todo); + for(size_t j{0u};j < NUM_LINES;j++) { - feedb_tap0 &= early_delay.Mask; - feedb_tap1 &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - maxz(feedb_tap0, feedb_tap1), todo - i)}; + size_t feedb_tap{offset - mEarly.Offset[j]}; + const float feedb_coeff{mEarly.Coeff[j]}; + float *RESTRICT out{al::assume_aligned<16>(outSamples[j].data() + base)}; - do { - fadeCount += 1.0f; - const ALfloat fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount}; - const ALfloat fade1{feedb_newCoeffStep*fadeCount}; - out[i] = mTempSamples[j][i] + - early_delay.Line[feedb_tap0++][j]*fade0 + - early_delay.Line[feedb_tap1++][j]*fade1; - ++i; - } while(--td); + for(size_t i{0u};i < todo;) + { + feedb_tap &= early_delay.Mask; + size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; + do { + tempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff; + out[i] = tempSamples[j][i]; + ++i; + } while(--td); + } } + + /* Finally, write the result to the late delay line input for the late + * reverb stage to pick up at the appropriate time, applying a scatter + * and bounce to improve the initial diffusion in the late reverb. + */ + VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, tempSamples, todo); + + base += todo; + offset += todo; } - for(size_t j{0u};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); +} - const size_t late_feed_tap{offset - mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); +void Modulation::calcDelays(size_t todo) +{ + constexpr float mod_scale{al::numbers::pi_v<float> * 2.0f / MOD_FRACONE}; + uint idx{Index}; + const uint step{Step}; + const float depth{Depth}; + for(size_t i{0};i < todo;++i) + { + idx += step; + const float lfo{std::sin(static_cast<float>(idx&MOD_FRACMASK) * mod_scale)}; + ModDelays[i] = (lfo+1.0f) * depth; + } + Index = idx; } + /* This generates the reverb tail using a modified feed-back delay network * (FDN). * - * Results from the early reflections are mixed with the output from the late - * delay lines. + * Results from the early reflections are mixed with the output from the + * modulated late delay lines. * * The late response is then completed by T60 and all-pass filtering the mix. * * Finally, the lines are reversed (so they feed their opposite directions) * and scattered with the FDN matrix before re-feeding the delay lines. - * - * Two variations are made, one for for transitional (cross-faded) delay line - * processing and one for non-transitional processing. */ -void ReverbState::lateUnfaded(const size_t offset, const size_t todo) +void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, + const al::span<ReverbUpdateLine, NUM_LINES> tempSamples, + const al::span<FloatBufferLine, NUM_LINES> outSamples) { const DelayLineI late_delay{mLate.Delay}; - const DelayLineI main_delay{mDelay}; - const ALfloat mixX{mMixX}; - const ALfloat mixY{mMixY}; + const DelayLineI in_delay{mLateDelayIn}; + const float mixX{mMixX}; + const float mixY{mMixY}; - ASSUME(todo > 0); + ASSUME(samplesToDo > 0); - /* First, load decorrelated samples from the main and feedback delay lines. - * Filter the signal to apply its frequency-dependent decay. - */ - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t base{0};base < samplesToDo;) { - size_t late_delay_tap{offset - mLateDelayTap[j][0]}; - size_t late_feedb_tap{offset - mLate.Offset[j][0]}; - const ALfloat midGain{mLate.T60[j].MidGain[0]}; - const ALfloat densityGain{mLate.DensityGain[0] * midGain}; - for(size_t i{0u};i < todo;) - { - late_delay_tap &= main_delay.Mask; - late_feedb_tap &= late_delay.Mask; - size_t td{minz(todo - i, - minz(main_delay.Mask+1 - late_delay_tap, late_delay.Mask+1 - late_feedb_tap))}; - do { - mTempSamples[j][i++] = - main_delay.Line[late_delay_tap++][j]*densityGain + - late_delay.Line[late_feedb_tap++][j]*midGain; - } while(--td); - } - mLate.T60[j].process(mTempSamples[j].data(), todo); - } - - /* Apply a vector all-pass to improve micro-surface diffusion, and write - * out the results for mixing. - */ - mLate.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;j++) - std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); - - /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); -} -void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloat fade, - const ALfloat fadeStep) -{ - const DelayLineI late_delay{mLate.Delay}; - const DelayLineI main_delay{mDelay}; - const ALfloat mixX{mMixX}; - const ALfloat mixY{mMixY}; + const size_t todo{minz(samplesToDo-base, minz(mLate.Offset[0], MAX_UPDATE_SAMPLES))}; + ASSUME(todo > 0); - ASSUME(todo > 0); + /* First, calculate the modulated delays for the late feedback. */ + mLate.Mod.calcDelays(todo); - for(size_t j{0u};j < NUM_LINES;j++) - { - const ALfloat oldMidGain{mLate.T60[j].MidGain[0]}; - const ALfloat midGain{mLate.T60[j].MidGain[1]}; - const ALfloat oldMidStep{-oldMidGain * fadeStep}; - const ALfloat midStep{midGain * fadeStep}; - const ALfloat oldDensityGain{mLate.DensityGain[0] * oldMidGain}; - const ALfloat densityGain{mLate.DensityGain[1] * midGain}; - const ALfloat oldDensityStep{-oldDensityGain * fadeStep}; - const ALfloat densityStep{densityGain * fadeStep}; - size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; - size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; - size_t late_feedb_tap0{offset - mLate.Offset[j][0]}; - size_t late_feedb_tap1{offset - mLate.Offset[j][1]}; - ALfloat fadeCount{fade}; - - for(size_t i{0u};i < todo;) + /* Next, load decorrelated samples from the main and feedback delay + * lines. Filter the signal to apply its frequency-dependent decay. + */ + const float fadeStep{1.0f / static_cast<float>(todo)}; + for(size_t j{0u};j < NUM_LINES;j++) { - late_delay_tap0 &= main_delay.Mask; - late_delay_tap1 &= main_delay.Mask; - late_feedb_tap0 &= late_delay.Mask; - late_feedb_tap1 &= late_delay.Mask; - size_t td{minz(todo - i, - minz(main_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1), - late_delay.Mask+1 - maxz(late_feedb_tap0, late_feedb_tap1)))}; - do { - fadeCount += 1.0f; - const ALfloat fade0{oldDensityGain + oldDensityStep*fadeCount}; - const ALfloat fade1{densityStep*fadeCount}; - const ALfloat gfade0{oldMidGain + oldMidStep*fadeCount}; - const ALfloat gfade1{midStep*fadeCount}; - mTempSamples[j][i++] = - main_delay.Line[late_delay_tap0++][j]*fade0 + - main_delay.Line[late_delay_tap1++][j]*fade1 + - late_delay.Line[late_feedb_tap0++][j]*gfade0 + - late_delay.Line[late_feedb_tap1++][j]*gfade1; - } while(--td); + size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; + size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; + size_t late_feedb_tap{offset - mLate.Offset[j]}; + const float midGain{mLate.T60[j].MidGain}; + const float densityGain{mLate.DensityGain * midGain}; + const float densityStep{late_delay_tap0 != late_delay_tap1 ? + densityGain*fadeStep : 0.0f}; + float fadeCount{0.0f}; + + for(size_t i{0u};i < todo;) + { + late_delay_tap0 &= in_delay.Mask; + late_delay_tap1 &= in_delay.Mask; + size_t td{minz(todo-i, in_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; + do { + /* Calculate the read offset and offset between it and the + * next sample. + */ + const float fdelay{mLate.Mod.ModDelays[i]}; + const size_t idelay{float2uint(fdelay * float{gCubicTable.sTableSteps})}; + const size_t delay{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; + const size_t delayoffset{idelay & gCubicTable.sTableMask}; + ++late_feedb_tap; + + /* Get the samples around by the delayed offset. */ + const float out0{late_delay.Line[(delay ) & late_delay.Mask][j]}; + const float out1{late_delay.Line[(delay-1) & late_delay.Mask][j]}; + const float out2{late_delay.Line[(delay-2) & late_delay.Mask][j]}; + const float out3{late_delay.Line[(delay-3) & late_delay.Mask][j]}; + + /* The output is obtained by interpolating the four samples + * that were acquired above, and combined with the main + * delay tap. + */ + const float out{out0*gCubicTable.getCoeff0(delayoffset) + + out1*gCubicTable.getCoeff1(delayoffset) + + out2*gCubicTable.getCoeff2(delayoffset) + + out3*gCubicTable.getCoeff3(delayoffset)}; + const float fade0{densityGain - densityStep*fadeCount}; + const float fade1{densityStep*fadeCount}; + fadeCount += 1.0f; + tempSamples[j][i] = out*midGain + + in_delay.Line[late_delay_tap0++][j]*fade0 + + in_delay.Line[late_delay_tap1++][j]*fade1; + ++i; + } while(--td); + } + mLateDelayTap[j][0] = mLateDelayTap[j][1]; + + mLate.T60[j].process({tempSamples[j].data(), todo}); } - mLate.T60[j].process(mTempSamples[j].data(), todo); - } - mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); - for(size_t j{0u};j < NUM_LINES;j++) - std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); + /* Apply a vector all-pass to improve micro-surface diffusion, and + * write out the results for mixing. + */ + mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); + for(size_t j{0u};j < NUM_LINES;j++) + std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base); + + /* Finally, scatter and bounce the results to refeed the feedback buffer. */ + VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, tempSamples, todo); - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); + base += todo; + offset += todo; + } } void ReverbState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - size_t offset{mOffset}; + const size_t offset{mOffset}; ASSUME(samplesToDo > 0); - /* Convert B-Format to A-Format for processing. */ - const size_t numInput{samplesIn.size()}; - const al::span<float> tmpspan{mTempLine.data(), samplesToDo}; - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - MixRowSamples(tmpspan, {B2A[c], numInput}, samplesIn[0].data(), samplesIn[0].size()); + auto &oldpipeline = mPipelines[mCurrentPipeline^1]; + auto &pipeline = mPipelines[mCurrentPipeline]; - /* Band-pass the incoming samples and feed the initial delay line. */ - mFilter[c].Lp.process(mTempLine.data(), mTempLine.data(), samplesToDo); - mFilter[c].Hp.process(mTempLine.data(), mTempLine.data(), samplesToDo); - mDelay.write(offset, c, mTempLine.data(), samplesToDo); - } - - /* Process reverb for these samples. */ - if LIKELY(!mDoFading) + if(mPipelineState >= Fading) { - for(size_t base{0};base < samplesToDo;) + /* Convert B-Format to A-Format for processing. */ + const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; + for(size_t c{0u};c < NUM_LINES;c++) { - /* Calculate the number of samples we can do this iteration. */ - size_t todo{minz(samplesToDo - base, mMaxUpdate[0])}; - /* Some mixers require maintaining a 4-sample alignment, so ensure - * that if it's not the last iteration. - */ - if(base+todo < samplesToDo) todo &= ~size_t{3}; - ASSUME(todo > 0); + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) + { + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - /* Generate non-faded early reflections and late reverb. */ - earlyUnfaded(offset, todo); - lateUnfaded(offset, todo); + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); + } - /* Finally, mix early reflections and late reverb. */ - (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); + /* Band-pass the incoming samples and feed the initial delay line. */ + auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + } + if(mPipelineState == Fading) + { + /* Give the old pipeline silence if it's still fading out. */ + for(size_t c{0u};c < NUM_LINES;c++) + { + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - offset += todo; - base += todo; + auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + } } } else { + /* At the start of a fade, fade in input for the current pipeline, and + * fade out input for the old pipeline. + */ + const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; const float fadeStep{1.0f / static_cast<float>(samplesToDo)}; - for(size_t base{0};base < samplesToDo;) - { - size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))}; - if(base+todo < samplesToDo) todo &= ~size_t{3}; - ASSUME(todo > 0); - /* Generate cross-faded early reflections and late reverb. */ - auto fadeCount = static_cast<ALfloat>(base); - earlyFaded(offset, todo, fadeCount, fadeStep); - lateFaded(offset, todo, fadeCount, fadeStep); + for(size_t c{0u};c < NUM_LINES;c++) + { + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) + { + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); + } + float stepCount{0.0f}; + for(float &sample : tmpspan) + { + stepCount += 1.0f; + sample *= stepCount*fadeStep; + } - offset += todo; - base += todo; + auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); } - - /* Update the cross-fading delay line taps. */ for(size_t c{0u};c < NUM_LINES;c++) { - mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1]; - mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1]; - mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1]; - mEarly.Offset[c][0] = mEarly.Offset[c][1]; - mEarly.Coeff[c][0] = mEarly.Coeff[c][1]; - mLateDelayTap[c][0] = mLateDelayTap[c][1]; - mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1]; - mLate.Offset[c][0] = mLate.Offset[c][1]; - mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1]; - } - mLate.DensityGain[0] = mLate.DensityGain[1]; - mMaxUpdate[0] = mMaxUpdate[1]; - mDoFading = false; - } - mOffset = offset; -} + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) + { + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); + } + float stepCount{0.0f}; + for(float &sample : tmpspan) + { + stepCount += 1.0f; + sample *= 1.0f - stepCount*fadeStep; + } -void EAXReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val != AL_FALSE; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param); - } -} -void EAXReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ EAXReverb_setParami(props, context, param, vals[0]); } -void EAXReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EAXREVERB_DENSITY: - if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_EAXREVERB_DIFFUSION: - if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_EAXREVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_EAXREVERB_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_EAXREVERB_GAINLF: - if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range"); - props->Reverb.GainLF = val; - break; - - case AL_EAXREVERB_DECAY_TIME: - if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range"); - props->Reverb.DecayLFRatio = val; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_EAXREVERB_ECHO_TIME: - if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range"); - props->Reverb.EchoTime = val; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range"); - props->Reverb.EchoDepth = val; - break; - - case AL_EAXREVERB_MODULATION_TIME: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range"); - props->Reverb.ModulationTime = val; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range"); - props->Reverb.ModulationDepth = val; - break; - - case AL_EAXREVERB_HFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range"); - props->Reverb.HFReference = val; - break; - - case AL_EAXREVERB_LFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range"); - props->Reverb.LFReference = val; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param); - } -} -void EAXReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) - { - case AL_EAXREVERB_REFLECTIONS_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range"); - props->Reverb.ReflectionsPan[0] = vals[0]; - props->Reverb.ReflectionsPan[1] = vals[1]; - props->Reverb.ReflectionsPan[2] = vals[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range"); - props->Reverb.LateReverbPan[0] = vals[0]; - props->Reverb.LateReverbPan[1] = vals[1]; - props->Reverb.LateReverbPan[2] = vals[2]; - break; - - default: - EAXReverb_setParamf(props, context, param, vals[0]); - break; + auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + } + mPipelineState = Fading; } -} -void EAXReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; + /* Process reverb for these samples. and mix them to the output. */ + pipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(pipeline, samplesOut, samplesToDo); - default: - context->setError(AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param); - } -} -void EAXReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ EAXReverb_getParami(props, context, param, vals); } -void EAXReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) + if(mPipelineState != Normal) { - case AL_EAXREVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_EAXREVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_EAXREVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_EAXREVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_EAXREVERB_GAINLF: - *val = props->Reverb.GainLF; - break; - - case AL_EAXREVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - *val = props->Reverb.DecayLFRatio; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_EAXREVERB_ECHO_TIME: - *val = props->Reverb.EchoTime; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - *val = props->Reverb.EchoDepth; - break; - - case AL_EAXREVERB_MODULATION_TIME: - *val = props->Reverb.ModulationTime; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - *val = props->Reverb.ModulationDepth; - break; - - case AL_EAXREVERB_HFREFERENCE: - *val = props->Reverb.HFReference; - break; - - case AL_EAXREVERB_LFREFERENCE: - *val = props->Reverb.LFReference; - break; + if(mPipelineState == Cleanup) + { + size_t numSamples{mSampleBuffer.size()/2}; + size_t pipelineOffset{numSamples * (mCurrentPipeline^1)}; + std::fill_n(mSampleBuffer.data()+pipelineOffset, numSamples, + decltype(mSampleBuffer)::value_type{}); - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; + oldpipeline.clear(); + mPipelineState = Normal; + } + else + { + /* If this is the final mix for this old pipeline, set the target + * gains to 0 to ensure a complete fade out, and set the state to + * Cleanup so the next invocation cleans up the delay buffers and + * filters. + */ + if(samplesToDo >= oldpipeline.mFadeSampleCount) + { + for(auto &gains : oldpipeline.mEarly.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : oldpipeline.mLate.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + oldpipeline.mFadeSampleCount = 0; + mPipelineState = Cleanup; + } + else + oldpipeline.mFadeSampleCount -= samplesToDo; - default: - context->setError(AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param); - } -} -void EAXReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ - switch(param) - { - case AL_EAXREVERB_REFLECTIONS_PAN: - vals[0] = props->Reverb.ReflectionsPan[0]; - vals[1] = props->Reverb.ReflectionsPan[1]; - vals[2] = props->Reverb.ReflectionsPan[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - vals[0] = props->Reverb.LateReverbPan[0]; - vals[1] = props->Reverb.LateReverbPan[1]; - vals[2] = props->Reverb.LateReverbPan[2]; - break; - - default: - EAXReverb_getParamf(props, context, param, vals); - break; + /* Process the old reverb for these samples. */ + oldpipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + oldpipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(oldpipeline, samplesOut, samplesToDo); + } } -} -DEFINE_ALEFFECT_VTABLE(EAXReverb); + mOffset = offset + samplesToDo; +} struct ReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &EAXReverb_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ReverbState{}}; } }; -EffectProps ReverbStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val != AL_FALSE; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ StdReverb_setParami(props, context, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_REVERB_DIFFUSION: - if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_REVERB_GAINHF: - if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_REVERB_DECAY_TIME: - if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_REVERB_DECAY_HFRATIO: - if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ StdReverb_setParamf(props, context, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ StdReverb_getParami(props, context, param, vals); } -void StdReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_REVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_REVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_REVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_REVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_REVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ StdReverb_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(StdReverb); - - struct StdReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &StdReverb_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new ReverbState{}}; } }; -EffectProps StdReverbStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = 1.0f; - props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = 1.0f; - props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = 0.0f; - props.Reverb.ReflectionsPan[1] = 0.0f; - props.Reverb.ReflectionsPan[2] = 0.0f; - props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = 0.0f; - props.Reverb.LateReverbPan[1] = 0.0f; - props.Reverb.LateReverbPan[2] = 0.0f; - props.Reverb.EchoTime = 0.25f; - props.Reverb.EchoDepth = 0.0f; - props.Reverb.ModulationTime = 0.25f; - props.Reverb.ModulationDepth = 0.0f; - props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = 5000.0f; - props.Reverb.LFReference = 250.0f; - props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - } // namespace EffectStateFactory *ReverbStateFactory_getFactory() diff --git a/alc/effects/vmorpher.cpp b/alc/effects/vmorpher.cpp index b1b7cc06..872c7add 100644 --- a/alc/effects/vmorpher.cpp +++ b/alc/effects/vmorpher.cpp @@ -1,38 +1,63 @@ /** - * 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: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html + * * Neither the name of Spherical-Harmonic-Transform nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" -#include <cmath> -#include <cstdlib> #include <algorithm> +#include <array> +#include <cstdlib> #include <functional> +#include <iterator> + +#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 "al/auxeffectslot.h" -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" namespace { -#define MAX_UPDATE_SAMPLES 128 +using uint = unsigned int; + +#define MAX_UPDATE_SAMPLES 256 #define NUM_FORMANTS 4 #define NUM_FILTERS 2 #define Q_FACTOR 5.0f @@ -44,22 +69,22 @@ namespace { #define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS) #define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1) -inline float Sin(ALuint index) +inline float Sin(uint index) { - constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE}; return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f; } -inline float Saw(ALuint index) +inline float Saw(uint index) { return static_cast<float>(index) / float{WAVEFORM_FRACONE}; } -inline float Triangle(ALuint index) +inline float Triangle(uint index) { return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); } -inline float Half(ALuint) { return 0.5f; } +inline float Half(uint) { return 0.5f; } -template<float (&func)(ALuint)> -void Oscillate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo) +template<float (&func)(uint)> +void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo) { for(size_t i{0u};i < todo;i++) { @@ -71,32 +96,32 @@ void Oscillate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo struct FormantFilter { - ALfloat mCoeff{0.0f}; - ALfloat mGain{1.0f}; - ALfloat mS1{0.0f}; - ALfloat mS2{0.0f}; + float mCoeff{0.0f}; + float mGain{1.0f}; + float mS1{0.0f}; + float mS2{0.0f}; FormantFilter() = default; - FormantFilter(ALfloat f0norm, ALfloat gain) - : mCoeff{std::tan(al::MathDefs<float>::Pi() * f0norm)}, mGain{gain} + FormantFilter(float f0norm, float gain) + : mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain} { } - inline void process(const ALfloat *samplesIn, ALfloat *samplesOut, const size_t numInput) + inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) { /* A state variable filter from a topology-preserving transform. * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg */ - const ALfloat g{mCoeff}; - const ALfloat gain{mGain}; - const ALfloat h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; - ALfloat s1{mS1}; - ALfloat s2{mS2}; + const float g{mCoeff}; + const float gain{mGain}; + const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; + float s1{mS1}; + float s2{mS2}; for(size_t i{0u};i < numInput;i++) { - const ALfloat H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; - const ALfloat B{g*H + s1}; - const ALfloat L{g*B + s2}; + const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; + const float B{g*H + s1}; + const float L{g*B + s2}; s1 = g*H + B; s2 = g*B + L; @@ -118,33 +143,40 @@ struct FormantFilter struct VmorpherState final : public EffectState { struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; + FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS]; /* Effect gains for each channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; + float mCurrentGain{}; + float mTargetGain{}; + } mChans[MaxAmbiChannels]; - void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){}; + void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; - ALuint mIndex{0}; - ALuint mStep{1}; + uint mIndex{0}; + uint mStep{1}; /* Effects buffers */ - ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{}; - ALfloat mSampleBufferB[MAX_UPDATE_SAMPLES]{}; + alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{}; + alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; + alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *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<const FloatBufferLine> samplesIn, + const al::span<FloatBufferLine> samplesOut) override; - static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch); + static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch); DEF_NEWDEL(VmorpherState) }; -std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch) +std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) { /* Using soprano formant set of values to * better match mid-range frequency space. @@ -153,79 +185,81 @@ std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, A */ switch(phoneme) { - case AL_VOCAL_MORPHER_PHONEME_A: + case VMorpherPhenome::A: return {{ {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */ {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */ {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */ }}; - case AL_VOCAL_MORPHER_PHONEME_E: + case VMorpherPhenome::E: return {{ {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */ {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */ {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ }}; - case AL_VOCAL_MORPHER_PHONEME_I: + case VMorpherPhenome::I: return {{ {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */ {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */ {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */ }}; - case AL_VOCAL_MORPHER_PHONEME_O: + case VMorpherPhenome::O: return {{ {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */ {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */ {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */ }}; - case AL_VOCAL_MORPHER_PHONEME_U: + case VMorpherPhenome::U: return {{ {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */ {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */ {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ }}; + default: + break; } return {}; } -ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/) +void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]), + e.mTargetChannel = InvalidChannelIndex; + std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), + std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mCurrentGain = 0.0f; } - - return AL_TRUE; } -void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; - const ALfloat frequency{static_cast<ALfloat>(device->Frequency)}; - const ALfloat step{props->Vmorpher.Rate / frequency}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1})); + const DeviceBase *device{context->mDevice}; + const float frequency{static_cast<float>(device->Frequency)}; + const float step{props->Vmorpher.Rate / frequency}; + mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); if(mStep == 0) mGetSamples = Oscillate<Half>; - else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SINUSOID) + else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid) mGetSamples = Oscillate<Sin>; - else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH) - mGetSamples = Oscillate<Saw>; - else /*if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE)*/ + else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle) mGetSamples = Oscillate<Triangle>; + else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/ + mGetSamples = Oscillate<Saw>; - const ALfloat pitchA{std::pow(2.0f, + const float pitchA{std::pow(2.0f, static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)}; - const ALfloat pitchB{std::pow(2.0f, + const float pitchB{std::pow(2.0f, static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)}; auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA); @@ -234,16 +268,17 @@ void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, /* Copy the filter coefficients to the input channels. */ for(size_t i{0u};i < slot->Wet.Buffer.size();++i) { - std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX])); + std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX])); + std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX])); } mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + auto set_channel = [this](size_t idx, uint outchan, float outgain) { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) @@ -253,41 +288,46 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float */ for(size_t base{0u};base < samplesToDo;) { - alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES]; const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; - mGetSamples(lfo, mIndex, mStep, td); - mIndex += static_cast<ALuint>(mStep * td); + mGetSamples(mLfo, mIndex, mStep, td); + mIndex += static_cast<uint>(mStep * td); mIndex &= WAVEFORM_FRACMASK; - auto chandata = std::addressof(mChans[0]); + auto chandata = std::begin(mChans); for(const auto &input : samplesIn) { - std::fill_n(std::begin(mSampleBufferA), td, 0.0f); - std::fill_n(std::begin(mSampleBufferB), td, 0.0f); + const size_t outidx{chandata->mTargetChannel}; + if(outidx == InvalidChannelIndex) + { + ++chandata; + continue; + } - auto& vowelA = chandata->Formants[VOWEL_A_INDEX]; - auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; + auto& vowelA = chandata->mFormants[VOWEL_A_INDEX]; + auto& vowelB = chandata->mFormants[VOWEL_B_INDEX]; /* Process first vowel. */ + std::fill_n(std::begin(mSampleBufferA), td, 0.0f); vowelA[0].process(&input[base], mSampleBufferA, td); vowelA[1].process(&input[base], mSampleBufferA, td); vowelA[2].process(&input[base], mSampleBufferA, td); vowelA[3].process(&input[base], mSampleBufferA, td); /* Process second vowel. */ + std::fill_n(std::begin(mSampleBufferB), td, 0.0f); vowelB[0].process(&input[base], mSampleBufferB, td); vowelB[1].process(&input[base], mSampleBufferB, td); vowelB[2].process(&input[base], mSampleBufferB, td); vowelB[3].process(&input[base], mSampleBufferB, td); - alignas(16) ALfloat blended[MAX_UPDATE_SAMPLES]; + alignas(16) float blended[MAX_UPDATE_SAMPLES]; for(size_t i{0u};i < td;i++) - blended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]); + blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ - MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); + MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo-base); ++chandata; } @@ -296,133 +336,11 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float } -void Vmorpher_setParami(EffectProps* props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_VOCAL_MORPHER_WAVEFORM: - if(!(val >= AL_VOCAL_MORPHER_MIN_WAVEFORM && val <= AL_VOCAL_MORPHER_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher waveform out of range"); - props->Vmorpher.Waveform = val; - break; - - case AL_VOCAL_MORPHER_PHONEMEA: - if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a out of range"); - props->Vmorpher.PhonemeA = val; - break; - - case AL_VOCAL_MORPHER_PHONEMEB: - if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b out of range"); - props->Vmorpher.PhonemeB = val; - break; - - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: - if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a coarse tuning out of range"); - props->Vmorpher.PhonemeACoarseTuning = val; - break; - - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: - if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b coarse tuning out of range"); - props->Vmorpher.PhonemeBCoarseTuning = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - param); - } -} -void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); } -void Vmorpher_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_VOCAL_MORPHER_RATE: - if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher rate out of range"); - props->Vmorpher.Rate = val; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - param); - } -} -void Vmorpher_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Vmorpher_setParamf(props, context, param, vals[0]); } - -void Vmorpher_getParami(const EffectProps* props, ALCcontext *context, ALenum param, ALint* val) -{ - switch(param) - { - case AL_VOCAL_MORPHER_PHONEMEA: - *val = props->Vmorpher.PhonemeA; - break; - - case AL_VOCAL_MORPHER_PHONEMEB: - *val = props->Vmorpher.PhonemeB; - break; - - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: - *val = props->Vmorpher.PhonemeACoarseTuning; - break; - - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: - *val = props->Vmorpher.PhonemeBCoarseTuning; - break; - - case AL_VOCAL_MORPHER_WAVEFORM: - *val = props->Vmorpher.Waveform; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - param); - } -} -void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); } -void Vmorpher_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_VOCAL_MORPHER_RATE: - *val = props->Vmorpher.Rate; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - param); - } -} -void Vmorpher_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Vmorpher_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Vmorpher); - - struct VmorpherStateFactory final : public EffectStateFactory { - EffectState *create() override { return new VmorpherState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Vmorpher_vtable; } + al::intrusive_ptr<EffectState> create() override + { return al::intrusive_ptr<EffectState>{new VmorpherState{}}; } }; -EffectProps VmorpherStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; - props.Vmorpher.PhonemeA = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA; - props.Vmorpher.PhonemeB = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB; - props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; - props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; - props.Vmorpher.Waveform = AL_VOCAL_MORPHER_DEFAULT_WAVEFORM; - return props; -} - } // namespace EffectStateFactory *VmorpherStateFactory_getFactory() diff --git a/alc/filters/biquad.cpp b/alc/filters/biquad.cpp deleted file mode 100644 index 8a8810e2..00000000 --- a/alc/filters/biquad.cpp +++ /dev/null @@ -1,126 +0,0 @@ - -#include "config.h" - -#include "biquad.h" - -#include <algorithm> -#include <cassert> -#include <cmath> - -#include "opthelpers.h" - - -template<typename Real> -void BiquadFilterR<Real>::setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ) -{ - // Limit gain to -100dB - assert(gain > 0.00001f); - - const Real w0{al::MathDefs<Real>::Tau() * f0norm}; - const Real sin_w0{std::sin(w0)}; - const Real cos_w0{std::cos(w0)}; - const Real alpha{sin_w0/2.0f * rcpQ}; - - Real sqrtgain_alpha_2; - Real a[3]{ 1.0f, 0.0f, 0.0f }; - Real b[3]{ 1.0f, 0.0f, 0.0f }; - - /* Calculate filter coefficients depending on filter type */ - switch(type) - { - case BiquadType::HighShelf: - sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha; - b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2); - b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 ); - b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2); - a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2; - a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 ); - a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2; - break; - case BiquadType::LowShelf: - sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha; - b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2); - b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 ); - b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2); - a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2; - a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 ); - a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2; - break; - case BiquadType::Peaking: - b[0] = 1.0f + alpha * gain; - b[1] = -2.0f * cos_w0; - b[2] = 1.0f - alpha * gain; - a[0] = 1.0f + alpha / gain; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha / gain; - break; - - case BiquadType::LowPass: - b[0] = (1.0f - cos_w0) / 2.0f; - b[1] = 1.0f - cos_w0; - b[2] = (1.0f - cos_w0) / 2.0f; - a[0] = 1.0f + alpha; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha; - break; - case BiquadType::HighPass: - b[0] = (1.0f + cos_w0) / 2.0f; - b[1] = -(1.0f + cos_w0); - b[2] = (1.0f + cos_w0) / 2.0f; - a[0] = 1.0f + alpha; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha; - break; - case BiquadType::BandPass: - b[0] = alpha; - b[1] = 0.0f; - b[2] = -alpha; - a[0] = 1.0f + alpha; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha; - break; - } - - mA1 = a[1] / a[0]; - mA2 = a[2] / a[0]; - mB0 = b[0] / a[0]; - mB1 = b[1] / a[0]; - mB2 = b[2] / a[0]; -} - -template<typename Real> -void BiquadFilterR<Real>::process(Real *dst, const Real *src, const size_t numsamples) -{ - ASSUME(numsamples > 0); - - const Real b0{mB0}; - const Real b1{mB1}; - const Real b2{mB2}; - const Real a1{mA1}; - const Real a2{mA2}; - Real z1{mZ1}; - Real z2{mZ2}; - - /* Processing loop is Transposed Direct Form II. This requires less storage - * compared to Direct Form I (only two delay components, instead of a four- - * sample history; the last two inputs and outputs), and works better for - * floating-point which favors summing similarly-sized values while being - * less bothered by overflow. - * - * See: http://www.earlevel.com/main/2003/02/28/biquads/ - */ - auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real - { - Real output = input*b0 + z1; - z1 = input*b1 - output*a1 + z2; - z2 = input*b2 - output*a2; - return output; - }; - std::transform(src, src+numsamples, dst, proc_sample); - - mZ1 = z1; - mZ2 = z2; -} - -template class BiquadFilterR<float>; -template class BiquadFilterR<double>; diff --git a/alc/filters/biquad.h b/alc/filters/biquad.h deleted file mode 100644 index 9af954ae..00000000 --- a/alc/filters/biquad.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef FILTERS_BIQUAD_H -#define FILTERS_BIQUAD_H - -#include <cmath> -#include <cstddef> -#include <utility> - -#include "math_defs.h" - - -/* Filters implementation is based on the "Cookbook formulae for audio - * EQ biquad filter coefficients" by Robert Bristow-Johnson - * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt - */ -/* Implementation note: For the shelf and peaking filters, the specified gain - * is for the centerpoint of the transition band. This better fits EFX filter - * behavior, which expects the shelf's reference frequency to reach the given - * gain. To set the gain for the shelf or peak itself, use the square root of - * the desired linear gain (or halve the dB gain). - */ - -enum class BiquadType { - /** EFX-style low-pass filter, specifying a gain and reference frequency. */ - HighShelf, - /** EFX-style high-pass filter, specifying a gain and reference frequency. */ - LowShelf, - /** Peaking filter, specifying a gain and reference frequency. */ - Peaking, - - /** Low-pass cut-off filter, specifying a cut-off frequency. */ - LowPass, - /** High-pass cut-off filter, specifying a cut-off frequency. */ - HighPass, - /** Band-pass filter, specifying a center frequency. */ - BandPass, -}; - -template<typename Real> -class BiquadFilterR { - /* Last two delayed components for direct form II. */ - Real mZ1{0.0f}, mZ2{0.0f}; - /* Transfer function coefficients "b" (numerator) */ - Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f}; - /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ - Real mA1{0.0f}, mA2{0.0f}; - -public: - void clear() noexcept { mZ1 = mZ2 = 0.0f; } - - /** - * Sets the filter state for the specified filter type and its parameters. - * - * \param type The type of filter to apply. - * \param gain The gain for the reference frequency response. Only used by - * the Shelf and Peaking filter types. - * \param f0norm The reference frequency normal (ref_freq / sample_rate). - * This is the center point for the Shelf, Peaking, and - * BandPass filter types, or the cutoff frequency for the - * LowPass and HighPass filter types. - * \param rcpQ The reciprocal of the Q coefficient for the filter's - * transition band. Can be generated from rcpQFromSlope or - * rcpQFromBandwidth as needed. - */ - void setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ); - - void copyParamsFrom(const BiquadFilterR &other) - { - mB0 = other.mB0; - mB1 = other.mB1; - mB2 = other.mB2; - mA1 = other.mA1; - mA2 = other.mA2; - } - - - void process(Real *dst, const Real *src, const size_t numsamples); - - /* Rather hacky. It's just here to support "manual" processing. */ - std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; } - void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } - Real processOne(const Real in, Real &z1, Real &z2) const noexcept - { - Real out{in*mB0 + z1}; - z1 = in*mB1 - out*mA1 + z2; - z2 = in*mB2 - out*mA2; - return out; - } - - /** - * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using - * the reference gain and shelf slope parameter. - * \param gain 0 < gain - * \param slope 0 < slope <= 1 - */ - static Real rcpQFromSlope(Real gain, Real slope) - { return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); } - - /** - * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the - * normalized reference frequency and bandwidth. - * \param f0norm 0 < f0norm < 0.5. - * \param bandwidth 0 < bandwidth - */ - static Real rcpQFromBandwidth(Real f0norm, Real bandwidth) - { - const Real w0{al::MathDefs<Real>::Tau() * f0norm}; - return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0)); - } -}; - -using BiquadFilter = BiquadFilterR<float>; - -#endif /* FILTERS_BIQUAD_H */ diff --git a/alc/filters/nfc.cpp b/alc/filters/nfc.cpp deleted file mode 100644 index e4436b27..00000000 --- a/alc/filters/nfc.cpp +++ /dev/null @@ -1,391 +0,0 @@ - -#include "config.h" - -#include "nfc.h" - -#include <algorithm> - -#include "opthelpers.h" - - -/* Near-field control filters are the basis for handling the near-field effect. - * The near-field effect is a bass-boost present in the directional components - * of a recorded signal, created as a result of the wavefront curvature (itself - * a function of sound distance). Proper reproduction dictates this be - * compensated for using a bass-cut given the playback speaker distance, to - * avoid excessive bass in the playback. - * - * For real-time rendered audio, emulating the near-field effect based on the - * sound source's distance, and subsequently compensating for it at output - * based on the speaker distances, can create a more realistic perception of - * sound distance beyond a simple 1/r attenuation. - * - * These filters do just that. Each one applies a low-shelf filter, created as - * the combination of a bass-boost for a given sound source distance (near- - * field emulation) along with a bass-cut for a given control/speaker distance - * (near-field compensation). - * - * Note that it is necessary to apply a cut along with the boost, since the - * boost alone is unstable in higher-order ambisonics as it causes an infinite - * DC gain (even first-order ambisonics requires there to be no DC offset for - * the boost to work). Consequently, ambisonics requires a control parameter to - * be used to avoid an unstable boost-only filter. NFC-HOA defines this control - * as a reference delay, calculated with: - * - * reference_delay = control_distance / speed_of_sound - * - * This means w0 (for input) or w1 (for output) should be set to: - * - * wN = 1 / (reference_delay * sample_rate) - * - * when dealing with NFC-HOA content. For FOA input content, which does not - * specify a reference_delay variable, w0 should be set to 0 to apply only - * near-field compensation for output. It's important that w1 be a finite, - * positive, non-0 value or else the bass-boost will become unstable again. - * Also, w0 should not be too large compared to w1, to avoid excessively loud - * low frequencies. - */ - -namespace { - -constexpr float B[5][4] = { - { 0.0f }, - { 1.0f }, - { 3.0f, 3.0f }, - { 3.6778f, 6.4595f, 2.3222f }, - { 4.2076f, 11.4877f, 5.7924f, 9.1401f } -}; - -NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept -{ - NfcFilter1 nfc{}; - float b_00, g_0; - float r; - - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* 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.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; -} - -void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept -{ - const float r{0.5f * w0}; - const float b_00{B[1][0] * r}; - const float g_0{1.0f + b_00}; - - nfc->gain = nfc->base_gain * g_0; - nfc->b1 = 2.0f * b_00 / g_0; -} - - -NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept -{ - NfcFilter2 nfc{}; - float b_10, b_11, g_1; - float r; - - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; - b_10 = B[2][0] * r; - b_11 = B[2][1] * r * r; - g_1 = 1.0f + b_10 + b_11; - - nfc.gain *= g_1; - nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.b2 = 4.0f * b_11 / g_1; - - /* 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; -} - -void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept -{ - const float r{0.5f * w0}; - const float b_10{B[2][0] * r}; - const float b_11{B[2][1] * r * r}; - const float g_1{1.0f + b_10 + b_11}; - - 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; -} - - -NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept -{ - NfcFilter3 nfc{}; - float b_10, b_11, g_1; - float b_00, g_0; - float r; - - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; - b_10 = B[3][0] * r; - b_11 = B[3][1] * r * r; - b_00 = B[3][2] * r; - g_1 = 1.0f + b_10 + b_11; - g_0 = 1.0f + b_00; - - nfc.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; -} - -void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept -{ - const float r{0.5f * w0}; - const float b_10{B[3][0] * r}; - const float b_11{B[3][1] * r * r}; - const float b_00{B[3][2] * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00}; - - 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; -} - - -NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept -{ - NfcFilter4 nfc{}; - float b_10, b_11, g_1; - float b_00, b_01, g_0; - float r; - - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; - 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.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; -} - -void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept -{ - const float r{0.5f * w0}; - const float b_10{B[4][0] * r}; - const float b_11{B[4][1] * r * r}; - const float b_00{B[4][2] * r}; - const float b_01{B[4][3] * r * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00 + b_01}; - - 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; -} - -} // namespace - -void NfcFilter::init(const float w1) noexcept -{ - first = NfcFilterCreate1(0.0f, w1); - second = NfcFilterCreate2(0.0f, w1); - third = NfcFilterCreate3(0.0f, w1); - fourth = NfcFilterCreate4(0.0f, w1); -} - -void NfcFilter::adjust(const float w0) noexcept -{ - NfcFilterAdjust1(&first, w0); - NfcFilterAdjust2(&second, w0); - NfcFilterAdjust3(&third, w0); - NfcFilterAdjust4(&fourth, w0); -} - - -void NfcFilter::process1(float *RESTRICT dst, const float *RESTRICT src, const size_t count) -{ - ASSUME(count > 0); - - const float gain{first.gain}; - const float b1{first.b1}; - const float a1{first.a1}; - float z1{first.z[0]}; - auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float - { - const float y{in*gain - a1*z1}; - const float out{y + b1*z1}; - z1 += y; - return out; - }; - std::transform(src, src+count, dst, proc_sample); - first.z[0] = z1; -} - -void NfcFilter::process2(float *RESTRICT dst, const float *RESTRICT src, const size_t count) -{ - ASSUME(count > 0); - - const float gain{second.gain}; - const float b1{second.b1}; - const float b2{second.b2}; - const float a1{second.a1}; - const float a2{second.a2}; - float z1{second.z[0]}; - float z2{second.z[1]}; - auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float - { - const float y{in*gain - a1*z1 - a2*z2}; - const float out{y + b1*z1 + b2*z2}; - z2 += z1; - z1 += y; - return out; - }; - std::transform(src, src+count, dst, proc_sample); - second.z[0] = z1; - second.z[1] = z2; -} - -void NfcFilter::process3(float *RESTRICT dst, const float *RESTRICT src, const size_t count) -{ - ASSUME(count > 0); - - const float gain{third.gain}; - const float b1{third.b1}; - const float b2{third.b2}; - const float b3{third.b3}; - const float a1{third.a1}; - const float a2{third.a2}; - const float a3{third.a3}; - float z1{third.z[0]}; - float z2{third.z[1]}; - float z3{third.z[2]}; - auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float - { - float y{in*gain - a1*z1 - a2*z2}; - float out{y + b1*z1 + b2*z2}; - z2 += z1; - z1 += y; - - y = out - a3*z3; - out = y + b3*z3; - z3 += y; - return out; - }; - std::transform(src, src+count, dst, proc_sample); - third.z[0] = z1; - third.z[1] = z2; - third.z[2] = z3; -} - -void NfcFilter::process4(float *RESTRICT dst, const float *RESTRICT src, const size_t count) -{ - ASSUME(count > 0); - - const float gain{fourth.gain}; - const float b1{fourth.b1}; - const float b2{fourth.b2}; - const float b3{fourth.b3}; - const float b4{fourth.b4}; - const float a1{fourth.a1}; - const float a2{fourth.a2}; - const float a3{fourth.a3}; - const float a4{fourth.a4}; - float z1{fourth.z[0]}; - float z2{fourth.z[1]}; - float z3{fourth.z[2]}; - float z4{fourth.z[3]}; - auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float - { - float y{in*gain - a1*z1 - a2*z2}; - float out{y + b1*z1 + b2*z2}; - z2 += z1; - z1 += y; - - y = out - a3*z3 - a4*z4; - out = y + b3*z3 + b4*z4; - z4 += z3; - z3 += y; - return out; - }; - std::transform(src, src+count, dst, proc_sample); - fourth.z[0] = z1; - fourth.z[1] = z2; - fourth.z[2] = z3; - fourth.z[3] = z4; -} diff --git a/alc/filters/nfc.h b/alc/filters/nfc.h deleted file mode 100644 index d2bf3339..00000000 --- a/alc/filters/nfc.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef FILTER_NFC_H -#define FILTER_NFC_H - -#include <cstddef> - - -struct NfcFilter1 { - float base_gain, gain; - float b1, a1; - float z[1]; -}; -struct NfcFilter2 { - float base_gain, gain; - float b1, b2, a1, a2; - float z[2]; -}; -struct NfcFilter3 { - float base_gain, gain; - float b1, b2, b3, a1, a2, a3; - float z[3]; -}; -struct NfcFilter4 { - float base_gain, gain; - float b1, b2, b3, b4, a1, a2, a3, a4; - float z[4]; -}; - -class NfcFilter { - NfcFilter1 first; - NfcFilter2 second; - NfcFilter3 third; - NfcFilter4 fourth; - -public: - /* NOTE: - * w0 = speed_of_sound / (source_distance * sample_rate); - * w1 = speed_of_sound / (control_distance * sample_rate); - * - * Generally speaking, the control distance should be approximately the - * average speaker distance, or based on the reference delay if outputing - * NFC-HOA. It must not be negative, 0, or infinite. The source distance - * should not be too small relative to the control distance. - */ - - void init(const float w1) noexcept; - void adjust(const float w0) noexcept; - - /* Near-field control filter for first-order ambisonic channels (1-3). */ - void process1(float *RESTRICT dst, const float *RESTRICT src, const size_t count); - - /* Near-field control filter for second-order ambisonic channels (4-8). */ - void process2(float *RESTRICT dst, const float *RESTRICT src, const size_t count); - - /* Near-field control filter for third-order ambisonic channels (9-15). */ - void process3(float *RESTRICT dst, const float *RESTRICT src, const size_t count); - - /* Near-field control filter for fourth-order ambisonic channels (16-24). */ - void process4(float *RESTRICT dst, const float *RESTRICT src, const size_t count); -}; - -#endif /* FILTER_NFC_H */ diff --git a/alc/filters/splitter.cpp b/alc/filters/splitter.cpp deleted file mode 100644 index c6218e70..00000000 --- a/alc/filters/splitter.cpp +++ /dev/null @@ -1,117 +0,0 @@ - -#include "config.h" - -#include "splitter.h" - -#include <algorithm> -#include <cmath> -#include <limits> - -#include "math_defs.h" -#include "opthelpers.h" - - -template<typename Real> -void BandSplitterR<Real>::init(Real f0norm) -{ - const Real w{f0norm * al::MathDefs<Real>::Tau()}; - const Real cw{std::cos(w)}; - if(cw > std::numeric_limits<float>::epsilon()) - mCoeff = (std::sin(w) - 1.0f) / cw; - else - mCoeff = cw * -0.5f; - - mLpZ1 = 0.0f; - mLpZ2 = 0.0f; - mApZ1 = 0.0f; -} - -template<typename Real> -void BandSplitterR<Real>::process(Real *hpout, Real *lpout, const Real *input, const size_t count) -{ - ASSUME(count > 0); - - const Real ap_coeff{mCoeff}; - const Real lp_coeff{mCoeff*0.5f + 0.5f}; - Real lp_z1{mLpZ1}; - Real lp_z2{mLpZ2}; - Real ap_z1{mApZ1}; - auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real - { - /* Low-pass sample processing. */ - Real d{(in - lp_z1) * lp_coeff}; - Real lp_y{lp_z1 + d}; - lp_z1 = lp_y + d; - - d = (lp_y - lp_z2) * lp_coeff; - lp_y = lp_z2 + d; - lp_z2 = lp_y + d; - - *(lpout++) = lp_y; - - /* All-pass sample processing. */ - Real ap_y{in*ap_coeff + ap_z1}; - ap_z1 = in - ap_y*ap_coeff; - - /* High-pass generated from removing low-passed output. */ - return ap_y - lp_y; - }; - std::transform(input, input+count, hpout, proc_sample); - mLpZ1 = lp_z1; - mLpZ2 = lp_z2; - mApZ1 = ap_z1; -} - -template<typename Real> -void BandSplitterR<Real>::applyHfScale(Real *samples, const Real hfscale, const size_t count) -{ - ASSUME(count > 0); - - 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 from removing low-passed output. */ - return (ap_y-lp_y)*hfscale + lp_y; - }; - std::transform(samples, samples+count, samples, proc_sample); - mLpZ1 = lp_z1; - mLpZ2 = lp_z2; - mApZ1 = ap_z1; -} - -template<typename Real> -void BandSplitterR<Real>::applyAllpass(Real *samples, const size_t count) const -{ - ASSUME(count > 0); - - const Real coeff{mCoeff}; - Real z1{0.0f}; - auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real - { - const Real out{in*coeff + z1}; - z1 = in - out*coeff; - return out; - }; - std::transform(samples, samples+count, samples, proc_sample); -} - - -template class BandSplitterR<float>; -template class BandSplitterR<double>; diff --git a/alc/filters/splitter.h b/alc/filters/splitter.h deleted file mode 100644 index 5117a244..00000000 --- a/alc/filters/splitter.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef FILTER_SPLITTER_H -#define FILTER_SPLITTER_H - -#include <cstddef> - - -/* Band splitter. Splits a signal into two phase-matching frequency bands. */ -template<typename Real> -class BandSplitterR { - Real mCoeff{0.0f}; - Real mLpZ1{0.0f}; - Real mLpZ2{0.0f}; - Real mApZ1{0.0f}; - -public: - BandSplitterR() = default; - BandSplitterR(const BandSplitterR&) = default; - BandSplitterR(Real f0norm) { init(f0norm); } - - void init(Real f0norm); - void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } - void process(Real *hpout, Real *lpout, const Real *input, const size_t count); - - void applyHfScale(Real *samples, const Real hfscale, const size_t count); - - /* 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 applyAllpass(Real *samples, const size_t count) const; -}; -using BandSplitter = BandSplitterR<float>; - -#endif /* FILTER_SPLITTER_H */ diff --git a/alc/fpu_modes.h b/alc/fpu_modes.h deleted file mode 100644 index 5465e9cf..00000000 --- a/alc/fpu_modes.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FPU_MODES_H -#define FPU_MODES_H - -class FPUCtl { -#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE)) - unsigned int sse_state{}; -#endif - bool in_mode{}; - -public: - FPUCtl(); - /* HACK: 32-bit targets for GCC seem to have a problem here with certain - * noexcept methods (which destructors are) causing an internal compiler - * error. No idea why it's these methods specifically, but this is needed - * to get it to compile. - */ - ~FPUCtl() noexcept(false) { leave(); } - - FPUCtl(const FPUCtl&) = delete; - FPUCtl& operator=(const FPUCtl&) = delete; - - void leave(); -}; - -#endif /* FPU_MODES_H */ diff --git a/alc/helpers.cpp b/alc/helpers.cpp deleted file mode 100644 index 4ea94c7d..00000000 --- a/alc/helpers.cpp +++ /dev/null @@ -1,649 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#ifdef _WIN32 -#ifdef __MINGW32__ -#define _WIN32_IE 0x501 -#else -#define _WIN32_IE 0x400 -#endif -#endif - -#include "config.h" - -#include <algorithm> -#include <cerrno> -#include <cstdarg> -#include <cstdlib> -#include <cstdio> -#include <cstring> -#include <mutex> -#include <string> - -#ifdef HAVE_DIRENT_H -#include <dirent.h> -#endif -#ifdef HAVE_INTRIN_H -#include <intrin.h> -#endif -#ifdef HAVE_CPUID_H -#include <cpuid.h> -#endif -#ifdef HAVE_SSE_INTRINSICS -#include <xmmintrin.h> -#endif -#ifdef HAVE_SYS_SYSCONF_H -#include <sys/sysconf.h> -#endif - -#ifdef HAVE_PROC_PIDPATH -#include <libproc.h> -#endif - -#ifdef __FreeBSD__ -#include <sys/types.h> -#include <sys/sysctl.h> -#endif - -#ifndef _WIN32 -#include <unistd.h> -#elif defined(_WIN32_IE) -#include <shlobj.h> -#endif - -#include "alcmain.h" -#include "almalloc.h" -#include "alfstream.h" -#include "alspan.h" -#include "alstring.h" -#include "compat.h" -#include "cpu_caps.h" -#include "fpu_modes.h" -#include "logging.h" -#include "strutils.h" -#include "vector.h" - - -#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = unsigned int; -static inline void get_cpuid(unsigned int f, reg_type *regs) -{ __get_cpuid(f, ®s[0], ®s[1], ®s[2], ®s[3]); } -#define CAN_GET_CPUID -#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = int; -static inline void get_cpuid(unsigned int f, reg_type *regs) -{ (__cpuid)(regs, f); } -#define CAN_GET_CPUID -#endif - -int CPUCapFlags = 0; - -void FillCPUCaps(int capfilter) -{ - int caps = 0; - -/* FIXME: We really should get this for all available CPUs in case different - * CPUs have different caps (is that possible on one machine?). */ -#ifdef CAN_GET_CPUID - union { - reg_type regs[4]; - char str[sizeof(reg_type[4])]; - } cpuinf[3]{}; - - get_cpuid(0, cpuinf[0].regs); - if(cpuinf[0].regs[0] == 0) - ERR("Failed to get CPUID\n"); - else - { - unsigned int maxfunc = cpuinf[0].regs[0]; - unsigned int maxextfunc; - - get_cpuid(0x80000000, cpuinf[0].regs); - maxextfunc = cpuinf[0].regs[0]; - - TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc); - - TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8); - if(maxextfunc >= 0x80000004) - { - get_cpuid(0x80000002, cpuinf[0].regs); - get_cpuid(0x80000003, cpuinf[1].regs); - get_cpuid(0x80000004, cpuinf[2].regs); - TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str); - } - - if(maxfunc >= 1) - { - get_cpuid(1, cpuinf[0].regs); - if((cpuinf[0].regs[3]&(1<<25))) - caps |= CPU_CAP_SSE; - if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26))) - caps |= CPU_CAP_SSE2; - if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0))) - caps |= CPU_CAP_SSE3; - if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19))) - caps |= CPU_CAP_SSE4_1; - } - } -#else - /* Assume support for whatever's supported if we can't check for it */ -#if defined(HAVE_SSE4_1) -#warning "Assuming SSE 4.1 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) -#warning "Assuming SSE 3 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) -#warning "Assuming SSE 2 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) -#warning "Assuming SSE run-time support!" - caps |= CPU_CAP_SSE; -#endif -#endif -#ifdef HAVE_NEON - al::ifstream file{"/proc/cpuinfo"}; - if(!file.is_open()) - ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n"); - else - { - std::string features; - - auto getline = [](std::istream &f, std::string &output) -> bool - { - while(f.good() && f.peek() == '\n') - f.ignore(); - return std::getline(f, output) && !output.empty(); - - }; - while(getline(file, features)) - { - if(features.compare(0, 10, "Features\t:", 10) == 0) - break; - } - file.close(); - - size_t extpos{9}; - while((extpos=features.find("neon", extpos+1)) != std::string::npos) - { - if((extpos == 0 || std::isspace(features[extpos-1])) && - (extpos+4 == features.length() || std::isspace(features[extpos+4]))) - { - caps |= CPU_CAP_NEON; - break; - } - } - } -#endif - - TRACE("Extensions:%s%s%s%s%s%s\n", - ((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), - ((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), - ((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), - ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), - ((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), - ((!capfilter) ? " -none-" : "") - ); - CPUCapFlags = caps & capfilter; -} - - -FPUCtl::FPUCtl() -{ -#if defined(HAVE_SSE_INTRINSICS) - this->sse_state = _mm_getcsr(); - unsigned int sseState = this->sse_state; - sseState |= 0x8000; /* set flush-to-zero */ - sseState |= 0x0040; /* set denormals-are-zero */ - _mm_setcsr(sseState); - -#elif defined(__GNUC__) && defined(HAVE_SSE) - - if((CPUCapFlags&CPU_CAP_SSE)) - { - __asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state)); - unsigned int sseState = this->sse_state; - sseState |= 0x8000; /* set flush-to-zero */ - if((CPUCapFlags&CPU_CAP_SSE2)) - sseState |= 0x0040; /* set denormals-are-zero */ - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState)); - } -#endif - - this->in_mode = true; -} - -void FPUCtl::leave() -{ - if(!this->in_mode) return; - -#if defined(HAVE_SSE_INTRINSICS) - _mm_setcsr(this->sse_state); - -#elif defined(__GNUC__) && defined(HAVE_SSE) - - if((CPUCapFlags&CPU_CAP_SSE)) - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state)); -#endif - this->in_mode = false; -} - - -#ifdef _WIN32 - -const PathNamePair &GetProcBinary() -{ - static PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; - - al::vector<WCHAR> fullpath(256); - DWORD len; - while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))) == fullpath.size()) - fullpath.resize(fullpath.size() << 1); - if(len == 0) - { - ERR("Failed to get process name: error %lu\n", GetLastError()); - return ret; - } - - fullpath.resize(len); - if(fullpath.back() != 0) - fullpath.push_back(0); - - auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); - sep = std::find(fullpath.rbegin()+1, sep, '/'); - if(sep != fullpath.rend()) - { - *sep = 0; - ret.fname = wstr_to_utf8(&*sep + 1); - ret.path = wstr_to_utf8(fullpath.data()); - } - else - ret.fname = wstr_to_utf8(fullpath.data()); - - TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; -} - - -void al_print(FILE *logfile, const char *fmt, ...) -{ - al::vector<char> dynmsg; - char stcmsg[256]; - char *str{stcmsg}; - - va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; - if UNLIKELY(msglen >= 0 && static_cast<size_t>(msglen) >= sizeof(stcmsg)) - { - dynmsg.resize(static_cast<size_t>(msglen) + 1u); - str = dynmsg.data(); - msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2); - } - va_end(args2); - va_end(args); - - std::wstring wstr{utf8_to_wstr(str)}; - fputws(wstr.c_str(), logfile); - fflush(logfile); -} - - -static inline int is_slash(int c) -{ return (c == '\\' || c == '/'); } - -static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results) -{ - std::string pathstr{path}; - pathstr += "\\*"; - pathstr += ext; - TRACE("Searching %s\n", pathstr.c_str()); - - std::wstring wpath{utf8_to_wstr(pathstr.c_str())}; - WIN32_FIND_DATAW fdata; - HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)}; - if(hdl == INVALID_HANDLE_VALUE) return; - - const auto base = results->size(); - - do { - results->emplace_back(); - std::string &str = results->back(); - str = path; - str += '\\'; - str += wstr_to_utf8(fdata.cFileName); - } while(FindNextFileW(hdl, &fdata)); - FindClose(hdl); - - const al::span<std::string> newlist{results->data()+base, results->size()-base}; - std::sort(newlist.begin(), newlist.end()); - for(const auto &name : newlist) - TRACE(" got %s\n", name.c_str()); -} - -al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard<std::mutex> _{search_lock}; - - /* If the path is absolute, use it directly. */ - al::vector<std::string> results; - if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2])) - { - std::string path{subdir}; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); - return results; - } - if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\') - { - DirectorySearch(subdir, ext, &results); - return results; - } - - std::string path; - - /* Search the app-local directory. */ - if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) - { - path = wstr_to_utf8(localpath->c_str()); - if(is_slash(path.back())) - path.pop_back(); - } - else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)}) - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - free(cwdbuf); - } - else - path = "."; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); - - /* Search the local and global data dirs. */ - static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; - for(int id : ids) - { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) - continue; - - path = wstr_to_utf8(buffer); - if(!is_slash(path.back())) - path += '\\'; - path += subdir; - std::replace(path.begin(), path.end(), '/', '\\'); - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority(void) -{ - bool failed = false; - if(RTPrioLevel > 0) - failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - if(failed) ERR("Failed to set priority level for thread\n"); -} - -#else - -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) -#include <pthread.h> -#include <sched.h> -#endif - -const PathNamePair &GetProcBinary() -{ - static PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; - - al::vector<char> pathname; -#ifdef __FreeBSD__ - size_t pathlen; - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1) - WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno)); - else - { - pathname.resize(pathlen + 1); - sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0); - pathname.resize(pathlen); - } -#endif -#ifdef HAVE_PROC_PIDPATH - if(pathname.empty()) - { - char procpath[PROC_PIDPATHINFO_MAXSIZE]{}; - const pid_t pid{getpid()}; - if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1) - ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno)); - else - pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); - } -#endif - if(pathname.empty()) - { - pathname.resize(256); - - const char *selfname{"/proc/self/exe"}; - ssize_t len{readlink(selfname, pathname.data(), pathname.size())}; - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/self/file"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/curproc/exe"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/curproc/file"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - - while(len > 0 && static_cast<size_t>(len) == pathname.size()) - { - pathname.resize(pathname.size() << 1); - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len <= 0) - { - WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); - return ret; - } - - pathname.resize(static_cast<size_t>(len)); - } - while(!pathname.empty() && pathname.back() == 0) - pathname.pop_back(); - - auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); - if(sep != pathname.crend()) - { - ret.path = std::string(pathname.cbegin(), sep.base()-1); - ret.fname = std::string(sep.base(), pathname.cend()); - } - else - ret.fname = std::string(pathname.cbegin(), pathname.cend()); - - TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; -} - - -void al_print(FILE *logfile, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(logfile, fmt, ap); - va_end(ap); - - fflush(logfile); -} - - -static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results) -{ - TRACE("Searching %s for *%s\n", path, ext); - DIR *dir{opendir(path)}; - if(!dir) return; - - const auto base = results->size(); - const size_t extlen{strlen(ext)}; - - struct dirent *dirent; - while((dirent=readdir(dir)) != nullptr) - { - if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) - continue; - - const size_t len{strlen(dirent->d_name)}; - if(len <= extlen) continue; - if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0) - continue; - - results->emplace_back(); - std::string &str = results->back(); - str = path; - if(str.back() != '/') - str.push_back('/'); - str += dirent->d_name; - } - closedir(dir); - - const al::span<std::string> newlist{results->data()+base, results->size()-base}; - std::sort(newlist.begin(), newlist.end()); - for(const auto &name : newlist) - TRACE(" got %s\n", name.c_str()); -} - -al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard<std::mutex> _{search_lock}; - - al::vector<std::string> results; - if(subdir[0] == '/') - { - DirectorySearch(subdir, ext, &results); - return results; - } - - /* Search the app-local directory. */ - if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) - DirectorySearch(localpath->c_str(), ext, &results); - else - { - al::vector<char> cwdbuf(256); - while(!getcwd(cwdbuf.data(), cwdbuf.size())) - { - if(errno != ERANGE) - { - cwdbuf.clear(); - break; - } - cwdbuf.resize(cwdbuf.size() << 1); - } - if(cwdbuf.empty()) - DirectorySearch(".", ext, &results); - else - { - DirectorySearch(cwdbuf.data(), ext, &results); - cwdbuf.clear(); - } - } - - // Search local data dir - if(auto datapath = al::getenv("XDG_DATA_HOME")) - { - std::string &path = *datapath; - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - else if(auto homepath = al::getenv("HOME")) - { - std::string &path = *homepath; - if(path.back() == '/') - path.pop_back(); - path += "/.local/share/"; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - - // Search global data dirs - std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; - - size_t curpos{0u}; - while(curpos < datadirs.size()) - { - size_t nextpos{datadirs.find(':', curpos)}; - - std::string path{(nextpos != std::string::npos) ? - datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)}; - curpos = nextpos; - - if(path.empty()) continue; - if(path.back() != '/') - path += '/'; - path += subdir; - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority() -{ - bool failed = false; -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) - if(RTPrioLevel > 0) - { - struct sched_param param; - /* Use the minimum real-time priority possible for now (on Linux this - * should be 1 for SCHED_RR) */ - param.sched_priority = sched_get_priority_min(SCHED_RR); - failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); - } -#else - /* Real-time priority not available */ - failed = (RTPrioLevel>0); -#endif - if(failed) - ERR("Failed to set priority level for thread\n"); -} - -#endif diff --git a/alc/hrtf.cpp b/alc/hrtf.cpp deleted file mode 100644 index 8e416cf1..00000000 --- a/alc/hrtf.cpp +++ /dev/null @@ -1,1424 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by Chris Robinson - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "hrtf.h" - -#include <algorithm> -#include <array> -#include <cassert> -#include <cctype> -#include <cstdint> -#include <cstdio> -#include <cstring> -#include <functional> -#include <fstream> -#include <iterator> -#include <memory> -#include <mutex> -#include <new> -#include <numeric> -#include <type_traits> -#include <utility> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "alfstream.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alspan.h" -#include "filters/splitter.h" -#include "logging.h" -#include "math_defs.h" -#include "opthelpers.h" - - -struct HrtfHandle { - std::unique_ptr<HrtfEntry> mEntry; - al::FlexArray<char> mFilename; - - HrtfHandle(size_t fname_len) : mFilename{fname_len} { } - - static std::unique_ptr<HrtfHandle> Create(size_t fname_len) - { return std::unique_ptr<HrtfHandle>{new (FamCount{fname_len}) HrtfHandle{fname_len}}; } - - DEF_FAM_NEWDEL(HrtfHandle, mFilename) -}; - -namespace { - -using namespace std::placeholders; - -using HrtfHandlePtr = std::unique_ptr<HrtfHandle>; - -/* Data set limits must be the same as or more flexible than those defined in - * the makemhr utility. - */ -#define MIN_IR_SIZE (8) -#define MAX_IR_SIZE (512) -#define MOD_IR_SIZE (2) - -#define MIN_FD_COUNT (1) -#define MAX_FD_COUNT (16) - -#define MIN_FD_DISTANCE (50) -#define MAX_FD_DISTANCE (2500) - -#define MIN_EV_COUNT (5) -#define MAX_EV_COUNT (181) - -#define MIN_AZ_COUNT (1) -#define MAX_AZ_COUNT (255) - -#define MAX_HRIR_DELAY (HRTF_HISTORY_LENGTH-1) - -constexpr ALchar magicMarker00[8]{'M','i','n','P','H','R','0','0'}; -constexpr ALchar magicMarker01[8]{'M','i','n','P','H','R','0','1'}; -constexpr ALchar magicMarker02[8]{'M','i','n','P','H','R','0','2'}; - -/* First value for pass-through coefficients (remaining are 0), used for omni- - * directional sounds. */ -constexpr ALfloat PassthruCoeff{0.707106781187f/*sqrt(0.5)*/}; - -std::mutex LoadedHrtfLock; -al::vector<HrtfHandlePtr> LoadedHrtfs; - - -class databuf final : public std::streambuf { - int_type underflow() override - { return traits_type::eof(); } - - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override - { - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - char_type *cur; - switch(whence) - { - case std::ios_base::beg: - if(offset < 0 || offset > egptr()-eback()) - return traits_type::eof(); - cur = eback() + offset; - break; - - case std::ios_base::cur: - if((offset >= 0 && offset > egptr()-gptr()) || - (offset < 0 && -offset > gptr()-eback())) - return traits_type::eof(); - cur = gptr() + offset; - break; - - case std::ios_base::end: - if(offset > 0 || -offset > egptr()-eback()) - return traits_type::eof(); - cur = egptr() + offset; - break; - - default: - return traits_type::eof(); - } - - setg(eback(), cur, egptr()); - return cur - eback(); - } - - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override - { - // Simplified version of seekoff - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - if(pos < 0 || pos > egptr()-eback()) - return traits_type::eof(); - - setg(eback(), eback() + static_cast<size_t>(pos), egptr()); - return pos; - } - -public: - databuf(const char_type *start_, const char_type *end_) noexcept - { - setg(const_cast<char_type*>(start_), const_cast<char_type*>(start_), - const_cast<char_type*>(end_)); - } -}; - -class idstream final : public std::istream { - databuf mStreamBuf; - -public: - idstream(const char *start_, const char *end_) - : std::istream{nullptr}, mStreamBuf{start_, end_} - { init(&mStreamBuf); } -}; - - -struct IdxBlend { ALsizei idx; ALfloat blend; }; -/* Calculate the elevation index given the polar elevation in radians. This - * will return an index between 0 and (evcount - 1). - */ -IdxBlend CalcEvIndex(ALsizei evcount, ALfloat ev) -{ - ev = (al::MathDefs<float>::Pi()*0.5f + ev) * static_cast<float>(evcount-1) / - al::MathDefs<float>::Pi(); - ALsizei idx{float2int(ev)}; - - return IdxBlend{mini(idx, evcount-1), ev-static_cast<float>(idx)}; -} - -/* Calculate the azimuth index given the polar azimuth in radians. This will - * return an index between 0 and (azcount - 1). - */ -IdxBlend CalcAzIndex(ALsizei azcount, ALfloat az) -{ - az = (al::MathDefs<float>::Tau()+az) * static_cast<float>(azcount) / - al::MathDefs<float>::Tau(); - ALsizei idx{float2int(az)}; - - return IdxBlend{idx%azcount, az-static_cast<float>(idx)}; -} - -} // namespace - - -/* Calculates static HRIR coefficients and delays for the given polar elevation - * and azimuth in radians. The coefficients are normalized. - */ -void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance, - ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2]) -{ - const ALfloat dirfact{1.0f - (spread / al::MathDefs<float>::Tau())}; - - const auto *field = Hrtf->field; - const auto *field_end = field + Hrtf->fdCount-1; - ALsizei ebase{0}; - while(distance < field->distance && field != field_end) - { - ebase += field->evCount; - ++field; - } - - /* Claculate the elevation indinces. */ - const auto elev0 = CalcEvIndex(field->evCount, elevation); - const ALsizei elev1_idx{mini(elev0.idx+1, field->evCount-1)}; - const ALsizei ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset}; - const ALsizei ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset}; - - /* Calculate azimuth indices. */ - const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth); - const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth); - - /* Calculate the HRIR indices to blend. */ - ALsizei idx[4]{ - ir0offset + az0.idx, - ir0offset + ((az0.idx+1) % Hrtf->elev[ebase + elev0.idx].azCount), - ir1offset + az1.idx, - ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount) - }; - - /* Calculate bilinear blending weights, attenuated according to the - * directional panning factor. - */ - const ALfloat blend[4]{ - (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact, - (1.0f-elev0.blend) * ( az0.blend) * dirfact, - ( elev0.blend) * (1.0f-az1.blend) * dirfact, - ( elev0.blend) * ( az1.blend) * dirfact - }; - - /* Calculate the blended HRIR delays. */ - delays[0] = fastf2i( - Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] + - Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3] - ); - delays[1] = fastf2i( - Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] + - Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3] - ); - - const ALuint irSize{Hrtf->irSize}; - ASSUME(irSize >= MIN_IR_SIZE); - - /* Calculate the sample offsets for the HRIR indices. */ - idx[0] *= HRIR_LENGTH; - idx[1] *= HRIR_LENGTH; - idx[2] *= HRIR_LENGTH; - idx[3] *= HRIR_LENGTH; - - /* Calculate the blended HRIR coefficients. */ - ALfloat *coeffout{al::assume_aligned<16>(&coeffs[0][0])}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill(coeffout+2, coeffout + HRIR_LENGTH*2, 0.0f); - for(ALsizei c{0};c < 4;c++) - { - const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]])}; - const ALfloat mult{blend[c]}; - auto blend_coeffs = [mult](const ALfloat src, const ALfloat coeff) noexcept -> ALfloat - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + irSize*2, coeffout, coeffout, blend_coeffs); - } -} - - -std::unique_ptr<DirectHrtfState> DirectHrtfState::Create(size_t num_chans) -{ - return std::unique_ptr<DirectHrtfState>{new (FamCount{num_chans}) DirectHrtfState{num_chans}}; -} - -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, - const al::span<const AngularPoint> AmbiPoints, const ALfloat (*AmbiMatrix)[MAX_AMBI_CHANNELS], - const ALfloat *AmbiOrderHFGain) -{ - using double2 = std::array<double,2>; - struct ImpulseResponse { - alignas(16) std::array<double2,HRIR_LENGTH> hrir; - ALuint ldelay, rdelay; - }; - - static const int OrderFromChan[MAX_AMBI_CHANNELS]{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }; - /* Set this to true for dual-band HRTF processing. May require better - * calculation of the new IR length to deal with the head and tail - * generated by the HF scaling. - */ - static constexpr bool DualBand{true}; - - ALuint min_delay{HRTF_HISTORY_LENGTH}; - ALuint max_delay{0}; - al::vector<ImpulseResponse> impres; impres.reserve(AmbiPoints.size()); - auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse - { - ImpulseResponse res; - - auto &field = Hrtf->field[0]; - - /* Calculate the elevation indices. */ - const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value); - const ALsizei elev1_idx{mini(elev0.idx+1, field.evCount-1)}; - const ALsizei ir0offset{Hrtf->elev[elev0.idx].irOffset}; - const ALsizei ir1offset{Hrtf->elev[elev1_idx].irOffset}; - - /* Calculate azimuth indices. */ - const auto az0 = CalcAzIndex(Hrtf->elev[elev0.idx].azCount, pt.Azim.value); - const auto az1 = CalcAzIndex(Hrtf->elev[elev1_idx].azCount, pt.Azim.value); - - /* Calculate the HRIR indices to blend. */ - const ALuint idx[4]{ - static_cast<ALuint>(ir0offset + az0.idx), - static_cast<ALuint>(ir0offset + ((az0.idx+1) % Hrtf->elev[elev0.idx].azCount)), - static_cast<ALuint>(ir1offset + az1.idx), - static_cast<ALuint>(ir1offset + ((az1.idx+1) % Hrtf->elev[elev1_idx].azCount))}; - - /* Calculate bilinear blending weights. */ - const ALfloat blend[4]{ - (1.0f-elev0.blend) * (1.0f-az0.blend), - (1.0f-elev0.blend) * ( az0.blend), - ( elev0.blend) * (1.0f-az1.blend), - ( elev0.blend) * ( az1.blend)}; - - /* Calculate the blended HRIR delays. */ - res.ldelay = fastf2u( - Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] + - Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]); - res.rdelay = fastf2u( - Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] + - Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]); - - const size_t irSize{Hrtf->irSize}; - ASSUME(irSize >= MIN_IR_SIZE); - - /* Calculate the blended HRIR coefficients. */ - double *coeffout{al::assume_aligned<16>(&res.hrir[0][0])}; - std::fill(coeffout, coeffout + HRIR_LENGTH*2, 0.0); - for(ALsizei c{0};c < 4;c++) - { - const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]*HRIR_LENGTH])}; - const ALfloat mult{blend[c]}; - auto blend_coeffs = [mult](const float src, const double coeff) noexcept -> double - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + HRIR_LENGTH*2, coeffout, coeffout, blend_coeffs); - } - - min_delay = minu(min_delay, minu(res.ldelay, res.rdelay)); - max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay)); - - return res; - }; - std::transform(AmbiPoints.begin(), AmbiPoints.end(), std::back_inserter(impres), calc_res); - - /* For dual-band processing, add a 16-sample delay to compensate for the HF - * scale on the minimum-phase response. - */ - static constexpr ALsizei base_delay{DualBand ? 16 : 0}; - const ALdouble xover_norm{400.0 / Hrtf->sampleRate}; - BandSplitterR<double> splitter{xover_norm}; - - auto tmpres = al::vector<std::array<double2,HRIR_LENGTH>>(state->Coeffs.size()); - auto tmpflt = al::vector<std::array<double,HRIR_LENGTH*4>>(3); - for(size_t c{0u};c < AmbiPoints.size();++c) - { - const al::span<const double2,HRIR_LENGTH> hrir{impres[c].hrir}; - const ALuint ldelay{impres[c].ldelay - min_delay + base_delay}; - const ALuint rdelay{impres[c].rdelay - min_delay + base_delay}; - - if /*constexpr*/(!DualBand) - { - /* For single-band decoding, apply the HF scale to the response. */ - for(size_t i{0u};i < state->Coeffs.size();++i) - { - const double mult{double{AmbiOrderHFGain[OrderFromChan[i]]} * AmbiMatrix[c][i]}; - const ALuint numirs{HRIR_LENGTH - maxu(ldelay, rdelay)}; - ALuint lidx{ldelay}, ridx{rdelay}; - for(ALuint j{0};j < numirs;++j) - { - tmpres[i][lidx++][0] += hrir[j][0] * mult; - tmpres[i][ridx++][1] += hrir[j][1] * mult; - } - } - continue; - } - - /* For dual-band processing, the HRIR needs to be split into low and - * high frequency responses. The band-splitter alone creates frequency- - * dependent phase-shifts, which is not ideal. To counteract it, - * combine it with a backwards phase-shift. - */ - - /* Load the (left) HRIR backwards, into a temp buffer with padding. */ - std::fill(tmpflt[2].begin(), tmpflt[2].end(), 0.0); - std::transform(hrir.cbegin(), hrir.cend(), tmpflt[2].rbegin() + HRIR_LENGTH*3, - [](const double2 &ir) noexcept -> double { return ir[0]; }); - - /* Apply the all-pass on the reversed signal and reverse the resulting - * sample array. This produces the forward response with a backwards - * phase-shift (+n degrees becomes -n degrees). - */ - splitter.applyAllpass(tmpflt[2].data(), tmpflt[2].size()); - std::reverse(tmpflt[2].begin(), tmpflt[2].end()); - - /* Now apply the band-splitter. This applies the normal phase-shift, - * which cancels out with the backwards phase-shift to get the original - * phase on the split signal. - */ - splitter.clear(); - splitter.process(tmpflt[0].data(), tmpflt[1].data(), tmpflt[2].data(), tmpflt[2].size()); - - /* Apply left ear response with delay and HF scale. */ - for(size_t i{0u};i < state->Coeffs.size();++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALuint j{HRIR_LENGTH*3 - ldelay}; - for(ALuint lidx{0};lidx < HRIR_LENGTH;++lidx,++j) - tmpres[i][lidx][0] += (tmpflt[0][j]*hfgain + tmpflt[1][j]) * mult; - } - - /* Now run the same process on the right HRIR. */ - std::fill(tmpflt[2].begin(), tmpflt[2].end(), 0.0); - std::transform(hrir.cbegin(), hrir.cend(), tmpflt[2].rbegin() + HRIR_LENGTH*3, - [](const double2 &ir) noexcept -> double { return ir[1]; }); - - splitter.applyAllpass(tmpflt[2].data(), tmpflt[2].size()); - std::reverse(tmpflt[2].begin(), tmpflt[2].end()); - - splitter.clear(); - splitter.process(tmpflt[0].data(), tmpflt[1].data(), tmpflt[2].data(), tmpflt[2].size()); - - for(size_t i{0u};i < state->Coeffs.size();++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALuint j{HRIR_LENGTH*3 - rdelay}; - for(ALuint ridx{0};ridx < HRIR_LENGTH;++ridx,++j) - tmpres[i][ridx][1] += (tmpflt[0][j]*hfgain + tmpflt[1][j]) * mult; - } - } - tmpflt.clear(); - impres.clear(); - - for(size_t i{0u};i < state->Coeffs.size();++i) - { - auto copy_arr = [](const double2 &in) noexcept -> float2 - { return float2{{static_cast<float>(in[0]), static_cast<float>(in[1])}}; }; - std::transform(tmpres[i].cbegin(), tmpres[i].cend(), state->Coeffs[i].begin(), - copy_arr); - } - tmpres.clear(); - - ALuint max_length{HRIR_LENGTH}; - /* Increase the IR size by double the base delay with dual-band processing - * to account for the head and tail from the HF response scale. - */ - const ALuint irsize{minu(Hrtf->irSize + base_delay*2, max_length)}; - max_length = minu(max_delay-min_delay + irsize, max_length); - - /* Round up to the next IR size multiple. */ - max_length += MOD_IR_SIZE-1; - max_length -= max_length%MOD_IR_SIZE; - - TRACE("Skipped delay: %u, max delay: %u, new FIR length: %u\n", min_delay, max_delay-min_delay, - max_length); - state->IrSize = max_length; -} - - -namespace { - -std::unique_ptr<HrtfEntry> CreateHrtfStore(ALuint rate, ALushort irSize, const ALuint fdCount, - const ALubyte *evCount, const ALushort *distance, const ALushort *azCount, - const ALushort *irOffset, ALushort irCount, const ALfloat (*coeffs)[2], - const ALubyte (*delays)[2], const char *filename) -{ - std::unique_ptr<HrtfEntry> Hrtf; - - ALuint evTotal{std::accumulate(evCount, evCount+fdCount, 0u)}; - size_t total{sizeof(HrtfEntry)}; - total = RoundUp(total, alignof(HrtfEntry::Field)); /* Align for field infos */ - total += sizeof(HrtfEntry::Field)*fdCount; - total = RoundUp(total, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */ - total += sizeof(Hrtf->elev[0])*evTotal; - total = RoundUp(total, 16); /* Align for coefficients using SIMD */ - total += sizeof(Hrtf->coeffs[0])*HRIR_LENGTH*irCount; - total += sizeof(Hrtf->delays[0])*irCount; - - Hrtf.reset(new (al_calloc(16, total)) HrtfEntry{}); - if(!Hrtf) - ERR("Out of memory allocating storage for %s.\n", filename); - else - { - InitRef(Hrtf->mRef, 1u); - Hrtf->sampleRate = rate; - Hrtf->irSize = irSize; - Hrtf->fdCount = fdCount; - - /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast<char*>(Hrtf.get()); - uintptr_t offset = sizeof(HrtfEntry); - - offset = RoundUp(offset, alignof(HrtfEntry::Field)); /* Align for field infos */ - auto field_ = reinterpret_cast<HrtfEntry::Field*>(base + offset); - offset += sizeof(field_[0])*fdCount; - - offset = RoundUp(offset, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */ - auto elev_ = reinterpret_cast<HrtfEntry::Elevation*>(base + offset); - offset += sizeof(elev_[0])*evTotal; - - offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ - auto coeffs_ = reinterpret_cast<ALfloat(*)[2]>(base + offset); - offset += sizeof(coeffs_[0])*HRIR_LENGTH*irCount; - - auto delays_ = reinterpret_cast<ALubyte(*)[2]>(base + offset); - offset += sizeof(delays_[0])*irCount; - - assert(offset == total); - - /* Copy input data to storage. */ - for(ALuint i{0};i < fdCount;i++) - { - field_[i].distance = distance[i] / 1000.0f; - field_[i].evCount = evCount[i]; - } - for(ALuint i{0};i < evTotal;i++) - { - elev_[i].azCount = azCount[i]; - elev_[i].irOffset = irOffset[i]; - } - for(ALuint i{0};i < irCount;i++) - { - for(ALuint j{0};j < ALuint{irSize};j++) - { - coeffs_[i*HRIR_LENGTH + j][0] = coeffs[i*irSize + j][0]; - coeffs_[i*HRIR_LENGTH + j][1] = coeffs[i*irSize + j][1]; - } - for(ALuint j{irSize};j < HRIR_LENGTH;j++) - { - coeffs_[i*HRIR_LENGTH + j][0] = 0.0f; - coeffs_[i*HRIR_LENGTH + j][1] = 0.0f; - } - } - for(ALuint i{0};i < irCount;i++) - { - delays_[i][0] = delays[i][0]; - delays_[i][1] = delays[i][1]; - } - - /* Finally, assign the storage pointers. */ - Hrtf->field = field_; - Hrtf->elev = elev_; - Hrtf->coeffs = coeffs_; - Hrtf->delays = delays_; - } - - return Hrtf; -} - -ALubyte GetLE_ALubyte(std::istream &data) -{ - return static_cast<ALubyte>(data.get()); -} - -ALshort GetLE_ALshort(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - return static_cast<ALshort>((ret^32768) - 32768); -} - -ALushort GetLE_ALushort(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - return static_cast<ALushort>(ret); -} - -ALint GetLE_ALint24(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - ret |= data.get() << 16; - return (ret^8388608) - 8388608; -} - -ALuint GetLE_ALuint(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - ret |= data.get() << 16; - ret |= data.get() << 24; - return static_cast<ALuint>(ret); -} - -std::unique_ptr<HrtfEntry> LoadHrtf00(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALushort irCount{GetLE_ALushort(data)}; - ALushort irSize{GetLE_ALushort(data)}; - ALubyte evCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - auto evOffset = al::vector<ALushort>(evCount); - for(auto &val : evOffset) - val = GetLE_ALushort(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(size_t i{1};i < evCount;i++) - { - if(evOffset[i] <= evOffset[i-1]) - { - ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", i, evOffset[i], evOffset[i-1]); - failed = AL_TRUE; - } - } - if(irCount <= evOffset.back()) - { - ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n", - evOffset.size()-1, evOffset.back(), irCount); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - auto azCount = al::vector<ALushort>(evCount); - for(size_t i{1};i < evCount;i++) - { - azCount[i-1] = static_cast<ALushort>(evOffset[i] - evOffset[i-1]); - if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", - i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - azCount.back() = static_cast<ALushort>(irCount - evOffset.back()); - if(azCount.back() < MIN_AZ_COUNT || azCount.back() > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n", - azCount.size()-1, azCount.back(), MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irCount); - auto delays = al::vector<std::array<ALubyte,2>>(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(size_t i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(size_t i{0};i < evCount;i++) - { - const ALushort evoffset{evOffset[i]}; - const ALushort azcount{azCount[i]}; - for(size_t j{0};j < azcount;j++) - { - const size_t lidx{evoffset + j}; - const size_t ridx{evoffset + ((azcount-j) % azcount)}; - - for(size_t k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static const ALushort distance{0}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]), - &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename); -} - -std::unique_ptr<HrtfEntry> LoadHrtf01(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALushort irSize{GetLE_ALubyte(data)}; - ALubyte evCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - auto azCount = al::vector<ALushort>(evCount); - std::generate(azCount.begin(), azCount.end(), std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(size_t i{0};i < evCount;++i) - { - if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, azCount[i], - MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - auto evOffset = al::vector<ALushort>(evCount); - evOffset[0] = 0; - ALushort irCount{azCount[0]}; - for(size_t i{1};i < evCount;i++) - { - evOffset[i] = static_cast<ALushort>(evOffset[i-1] + azCount[i-1]); - irCount = static_cast<ALushort>(irCount + azCount[i]); - } - - auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irCount); - auto delays = al::vector<std::array<ALubyte,2>>(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(size_t i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(size_t i{0};i < evCount;i++) - { - const ALushort evoffset{evOffset[i]}; - const ALushort azcount{azCount[i]}; - for(size_t j{0};j < azcount;j++) - { - const size_t lidx{evoffset + j}; - const size_t ridx{evoffset + ((azcount-j) % azcount)}; - - for(size_t k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static const ALushort distance{0}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]), - &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename); -} - -#define SAMPLETYPE_S16 0 -#define SAMPLETYPE_S24 1 - -#define CHANTYPE_LEFTONLY 0 -#define CHANTYPE_LEFTRIGHT 1 - -std::unique_ptr<HrtfEntry> LoadHrtf02(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALubyte sampleType{GetLE_ALubyte(data)}; - ALubyte channelType{GetLE_ALubyte(data)}; - ALushort irSize{GetLE_ALubyte(data)}; - ALubyte fdCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(sampleType > SAMPLETYPE_S24) - { - ERR("Unsupported sample type: %d\n", sampleType); - failed = AL_TRUE; - } - if(channelType > CHANTYPE_LEFTRIGHT) - { - ERR("Unsupported channel type: %d\n", channelType); - failed = AL_TRUE; - } - - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(fdCount < 1 || fdCount > MAX_FD_COUNT) - { - ERR("Multiple field-depths not supported: fdCount=%d (%d to %d)\n", - fdCount, MIN_FD_COUNT, MAX_FD_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - auto distance = al::vector<ALushort>(fdCount); - auto evCount = al::vector<ALubyte>(fdCount); - auto azCount = al::vector<ALushort>{}; - for(size_t f{0};f < fdCount;f++) - { - distance[f] = GetLE_ALushort(data); - evCount[f] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - if(distance[f] < MIN_FD_DISTANCE || distance[f] > MAX_FD_DISTANCE) - { - ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance[f], - MIN_FD_DISTANCE, MAX_FD_DISTANCE); - failed = AL_TRUE; - } - if(f > 0 && distance[f] <= distance[f-1]) - { - ERR("Field distance[%zu] is not after previous (%d > %d)\n", f, distance[f], - distance[f-1]); - failed = AL_TRUE; - } - if(evCount[f] < MIN_EV_COUNT || evCount[f] > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount[f], - MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - const size_t ebase{azCount.size()}; - azCount.resize(ebase + evCount[f]); - std::generate(azCount.begin()+static_cast<ptrdiff_t>(ebase), azCount.end(), - std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(size_t e{0};e < evCount[f];e++) - { - if(azCount[ebase+e] < MIN_AZ_COUNT || azCount[ebase+e] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e, - azCount[ebase+e], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - } - - auto evOffset = al::vector<ALushort>(azCount.size()); - evOffset[0] = 0; - std::partial_sum(azCount.cbegin(), azCount.cend()-1, evOffset.begin()+1); - const auto irTotal = static_cast<ALushort>(evOffset.back() + azCount.back()); - - auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irTotal); - auto delays = al::vector<std::array<ALubyte,2>>(irTotal); - if(channelType == CHANTYPE_LEFTONLY) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - val[0] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f; - } - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(size_t i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - else if(channelType == CHANTYPE_LEFTRIGHT) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - { - val[0] = GetLE_ALshort(data) / 32768.0f; - val[1] = GetLE_ALshort(data) / 32768.0f; - } - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - { - val[0] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f; - val[1] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f; - } - } - for(auto &val : delays) - { - val[0] = GetLE_ALubyte(data); - val[1] = GetLE_ALubyte(data); - } - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(size_t i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - if(delays[i][1] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%zu][1]: %d (%d)\n", i, delays[i][1], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - if(failed) - return nullptr; - - if(channelType == CHANTYPE_LEFTONLY) - { - /* Mirror the left ear responses to the right ear. */ - size_t ebase{0}; - for(size_t f{0};f < fdCount;f++) - { - for(size_t e{0};e < evCount[f];e++) - { - const ALushort evoffset{evOffset[ebase+e]}; - const ALushort azcount{azCount[ebase+e]}; - for(size_t a{0};a < azcount;a++) - { - const size_t lidx{evoffset + a}; - const size_t ridx{evoffset + ((azcount-a) % azcount)}; - - for(size_t k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - ebase += evCount[f]; - } - } - - if(fdCount > 1) - { - auto distance_ = al::vector<ALushort>(distance.size()); - auto evCount_ = al::vector<ALubyte>(evCount.size()); - auto azCount_ = al::vector<ALushort>(azCount.size()); - auto evOffset_ = al::vector<ALushort>(evOffset.size()); - auto coeffs_ = al::vector<float2>(coeffs.size()); - auto delays_ = al::vector<std::array<ALubyte,2>>(delays.size()); - - /* Simple reverse for the per-field elements. */ - std::reverse_copy(distance.cbegin(), distance.cend(), distance_.begin()); - std::reverse_copy(evCount.cbegin(), evCount.cend(), evCount_.begin()); - - /* Each field has a group of elevations, which each have an azimuth - * count. Reverse the order of the groups, keeping the relative order - * of per-group azimuth counts. - */ - auto azcnt_end = azCount_.end(); - auto copy_azs = [&azCount,&azcnt_end](const ptrdiff_t ebase, const ALubyte num_evs) -> ptrdiff_t - { - auto azcnt_src = azCount.begin()+ebase; - azcnt_end = std::copy_backward(azcnt_src, azcnt_src+num_evs, azcnt_end); - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), ptrdiff_t{0}, copy_azs); - assert(azCount_.begin() == azcnt_end); - - /* Reestablish the IR offset for each elevation index, given the new - * ordering of elevations. - */ - evOffset_[0] = 0; - std::partial_sum(azCount_.cbegin(), azCount_.cend()-1, evOffset_.begin()+1); - - /* Reverse the order of each field's group of IRs. */ - auto coeffs_end = coeffs_.end(); - auto delays_end = delays_.end(); - auto copy_irs = [irSize,&azCount,&coeffs,&delays,&coeffs_end,&delays_end](const ptrdiff_t ebase, const ALubyte num_evs) -> ptrdiff_t - { - const ALsizei abase{std::accumulate(azCount.cbegin(), azCount.cbegin()+ebase, 0)}; - const ALsizei num_azs{std::accumulate(azCount.cbegin()+ebase, - azCount.cbegin() + (ebase+num_evs), 0)}; - - coeffs_end = std::copy_backward(coeffs.cbegin() + abase*irSize, - coeffs.cbegin() + (abase+num_azs)*irSize, coeffs_end); - delays_end = std::copy_backward(delays.cbegin() + abase, - delays.cbegin() + (abase+num_azs), delays_end); - - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), ptrdiff_t{0}, copy_irs); - assert(coeffs_.begin() == coeffs_end); - assert(delays_.begin() == delays_end); - - distance = std::move(distance_); - evCount = std::move(evCount_); - azCount = std::move(azCount_); - evOffset = std::move(evOffset_); - coeffs = std::move(coeffs_); - delays = std::move(delays_); - } - - return CreateHrtfStore(rate, irSize, fdCount, evCount.data(), distance.data(), azCount.data(), - evOffset.data(), irTotal, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]), - &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename); -} - - -bool checkName(al::vector<EnumeratedHrtf> &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const EnumeratedHrtf &entry) - { return name == entry.name; } - ) != list.cend(); -} - -void AddFileEntry(al::vector<EnumeratedHrtf> &list, const std::string &filename) -{ - /* Check if this file has already been loaded globally. */ - auto loaded_entry = LoadedHrtfs.begin(); - for(;loaded_entry != LoadedHrtfs.end();++loaded_entry) - { - if(filename != (*loaded_entry)->mFilename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - const char *new_mark{""}; - if(loaded_entry == LoadedHrtfs.end()) - { - new_mark = " (new)"; - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+1)); - loaded_entry = LoadedHrtfs.end()-1; - std::copy(filename.begin(), filename.end(), (*loaded_entry)->mFilename.begin()); - (*loaded_entry)->mFilename.back() = '\0'; - } - - /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - size_t namepos = filename.find_last_of('/')+1; - if(!namepos) namepos = filename.find_last_of('\\')+1; - - size_t extpos{filename.find_last_of('.')}; - if(extpos <= namepos) extpos = std::string::npos; - - const std::string basename{(extpos == std::string::npos) ? - filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; - std::string newname{basename}; - int count{1}; - while(checkName(list, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()}); - const EnumeratedHrtf &entry = list.back(); - - TRACE("Adding file entry \"%s\"%s\n", entry.name.c_str(), new_mark); -} - -/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer - * for input instead of opening the given filename. - */ -void AddBuiltInEntry(al::vector<EnumeratedHrtf> &list, const std::string &filename, ALuint residx) -{ - auto loaded_entry = LoadedHrtfs.begin(); - for(;loaded_entry != LoadedHrtfs.end();++loaded_entry) - { - if(filename != (*loaded_entry)->mFilename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - const char *new_mark{""}; - if(loaded_entry == LoadedHrtfs.end()) - { - new_mark = " (new)"; - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+32)); - loaded_entry = LoadedHrtfs.end()-1; - snprintf((*loaded_entry)->mFilename.data(), (*loaded_entry)->mFilename.size(), "!%u_%s", - residx, filename.c_str()); - } - - /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - - std::string newname{filename}; - int count{1}; - while(checkName(list, newname)) - { - newname = filename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()}); - const EnumeratedHrtf &entry = list.back(); - - TRACE("Adding built-in entry \"%s\"%s\n", entry.name.c_str(), new_mark); -} - - -#define IDR_DEFAULT_44100_MHR 1 -#define IDR_DEFAULT_48000_MHR 2 - -using ResData = al::span<const char>; -#ifndef ALSOFT_EMBED_HRTF_DATA - -ResData GetResource(int /*name*/) -{ return ResData{}; } - -#else - -#include "default-44100.mhr.h" -#include "default-48000.mhr.h" - -ResData GetResource(int name) -{ - if(name == IDR_DEFAULT_44100_MHR) - return {reinterpret_cast<const char*>(hrtf_default_44100), sizeof(hrtf_default_44100)}; - if(name == IDR_DEFAULT_48000_MHR) - return {reinterpret_cast<const char*>(hrtf_default_48000), sizeof(hrtf_default_48000)}; - return ResData{}; -} -#endif - -} // namespace - - -al::vector<EnumeratedHrtf> EnumerateHrtf(const char *devname) -{ - al::vector<EnumeratedHrtf> list; - - bool usedefaults{true}; - if(auto pathopt = ConfigValueStr(devname, nullptr, "hrtf-paths")) - { - const char *pathlist{pathopt->c_str()}; - while(pathlist && *pathlist) - { - const char *next, *end; - - while(isspace(*pathlist) || *pathlist == ',') - pathlist++; - if(*pathlist == '\0') - continue; - - next = strchr(pathlist, ','); - if(next) - end = next++; - else - { - end = pathlist + strlen(pathlist); - usedefaults = false; - } - - while(end != pathlist && isspace(*(end-1))) - --end; - if(end != pathlist) - { - const std::string pname{pathlist, end}; - for(const auto &fname : SearchDataFiles(".mhr", pname.c_str())) - AddFileEntry(list, fname); - } - - pathlist = next; - } - } - - if(usedefaults) - { - for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) - AddFileEntry(list, fname); - - if(!GetResource(IDR_DEFAULT_44100_MHR).empty()) - AddBuiltInEntry(list, "Built-In 44100hz", IDR_DEFAULT_44100_MHR); - - if(!GetResource(IDR_DEFAULT_48000_MHR).empty()) - AddBuiltInEntry(list, "Built-In 48000hz", IDR_DEFAULT_48000_MHR); - } - - if(auto defhrtfopt = ConfigValueStr(devname, nullptr, "default-hrtf")) - { - auto find_entry = [&defhrtfopt](const EnumeratedHrtf &entry) -> bool - { return entry.name == *defhrtfopt; }; - auto iter = std::find_if(list.begin(), list.end(), find_entry); - 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; -} - -HrtfEntry *GetLoadedHrtf(HrtfHandle *handle) -{ - std::lock_guard<std::mutex> _{LoadedHrtfLock}; - - if(handle->mEntry) - { - HrtfEntry *hrtf{handle->mEntry.get()}; - hrtf->IncRef(); - return hrtf; - } - - std::unique_ptr<std::istream> stream; - const char *name{""}; - ALint residx{}; - char ch{}; - if(sscanf(handle->mFilename.data(), "!%d%c", &residx, &ch) == 2 && ch == '_') - { - name = strchr(handle->mFilename.data(), ch)+1; - - TRACE("Loading %s...\n", name); - ResData res{GetResource(residx)}; - if(res.empty()) - { - ERR("Could not get resource %u, %s\n", residx, name); - return nullptr; - } - stream = al::make_unique<idstream>(res.begin(), res.end()); - } - else - { - name = handle->mFilename.data(); - - TRACE("Loading %s...\n", handle->mFilename.data()); - auto fstr = al::make_unique<al::ifstream>(handle->mFilename.data(), std::ios::binary); - if(!fstr->is_open()) - { - ERR("Could not open %s\n", handle->mFilename.data()); - return nullptr; - } - stream = std::move(fstr); - } - - std::unique_ptr<HrtfEntry> hrtf; - char magic[sizeof(magicMarker02)]; - stream->read(magic, sizeof(magic)); - if(stream->gcount() < static_cast<std::streamsize>(sizeof(magicMarker02))) - ERR("%s data is too short (%zu bytes)\n", name, stream->gcount()); - else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0) - { - TRACE("Detected data set format v2\n"); - hrtf = LoadHrtf02(*stream, name); - } - else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0) - { - TRACE("Detected data set format v1\n"); - hrtf = LoadHrtf01(*stream, name); - } - else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0) - { - TRACE("Detected data set format v0\n"); - hrtf = LoadHrtf00(*stream, name); - } - else - ERR("Invalid header in %s: \"%.8s\"\n", name, magic); - stream.reset(); - - if(!hrtf) - { - ERR("Failed to load %s\n", name); - return nullptr; - } - - TRACE("Loaded HRTF support for sample rate: %uhz\n", hrtf->sampleRate); - handle->mEntry = std::move(hrtf); - - return handle->mEntry.get(); -} - - -void HrtfEntry::IncRef() -{ - auto ref = IncrementRef(mRef); - TRACE("HrtfEntry %p increasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref); -} - -void HrtfEntry::DecRef() -{ - auto ref = DecrementRef(mRef); - TRACE("HrtfEntry %p decreasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref); - if(ref == 0) - { - std::lock_guard<std::mutex> _{LoadedHrtfLock}; - - /* Go through and clear all unused HRTFs. */ - auto delete_unused = [](HrtfHandlePtr &handle) -> void - { - HrtfEntry *entry{handle->mEntry.get()}; - if(entry && ReadRef(entry->mRef) == 0) - { - TRACE("Unloading unused HRTF %s\n", handle->mFilename.data()); - handle->mEntry = nullptr; - } - }; - std::for_each(LoadedHrtfs.begin(), LoadedHrtfs.end(), delete_unused); - } -} diff --git a/alc/hrtf.h b/alc/hrtf.h deleted file mode 100644 index 98df801b..00000000 --- a/alc/hrtf.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef ALC_HRTF_H -#define ALC_HRTF_H - -#include <array> -#include <cstddef> -#include <memory> -#include <string> - -#include "AL/al.h" - -#include "almalloc.h" -#include "alspan.h" -#include "ambidefs.h" -#include "atomic.h" -#include "vector.h" - -struct HrtfHandle; - - -#define HRTF_HISTORY_BITS (6) -#define HRTF_HISTORY_LENGTH (1<<HRTF_HISTORY_BITS) -#define HRTF_HISTORY_MASK (HRTF_HISTORY_LENGTH-1) - -#define HRIR_BITS (7) -#define HRIR_LENGTH (1<<HRIR_BITS) -#define HRIR_MASK (HRIR_LENGTH-1) - - -struct HrtfEntry { - RefCount mRef; - - ALuint sampleRate; - ALuint irSize; - - struct Field { - ALfloat distance; - ALubyte evCount; - }; - /* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and - * field[fdCount-1] is the nearest. - */ - ALuint fdCount; - const Field *field; - - struct Elevation { - ALushort azCount; - ALushort irOffset; - }; - Elevation *elev; - const ALfloat (*coeffs)[2]; - const ALubyte (*delays)[2]; - - void IncRef(); - void DecRef(); - - DEF_PLACE_NEWDEL() -}; - -struct EnumeratedHrtf { - std::string name; - - HrtfHandle *hrtf; -}; - - -using float2 = std::array<float,2>; -using HrirArray = std::array<float2,HRIR_LENGTH>; - -struct HrtfState { - alignas(16) std::array<ALfloat,HRTF_HISTORY_LENGTH> History; -}; - -struct HrtfFilter { - alignas(16) HrirArray Coeffs; - ALsizei Delay[2]; - ALfloat Gain; -}; - -struct DirectHrtfState { - /* HRTF filter state for dry buffer content */ - ALuint IrSize{0}; - al::FlexArray<HrirArray,16> Coeffs; - - DirectHrtfState(size_t numchans) : Coeffs{numchans} { } - - static std::unique_ptr<DirectHrtfState> Create(size_t num_chans); - - DEF_FAM_NEWDEL(DirectHrtfState, Coeffs) -}; - -struct ElevRadius { float value; }; -struct AzimRadius { float value; }; -struct AngularPoint { - ElevRadius Elev; - AzimRadius Azim; -}; - - -al::vector<EnumeratedHrtf> EnumerateHrtf(const char *devname); -HrtfEntry *GetLoadedHrtf(HrtfHandle *handle); - -void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance, - ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2]); - -/** - * Produces HRTF filter coefficients for decoding B-Format, given a set of - * virtual speaker positions, a matching decoding matrix, and per-order high- - * frequency gains for the decoder. The calculated impulse responses are - * ordered and scaled according to the matrix input. - */ -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, - const al::span<const AngularPoint> AmbiPoints, const ALfloat (*AmbiMatrix)[MAX_AMBI_CHANNELS], - const ALfloat *AmbiOrderHFGain); - -#endif /* ALC_HRTF_H */ diff --git a/alc/inprogext.h b/alc/inprogext.h index ad3ea288..ccb9a4be 100644 --- a/alc/inprogext.h +++ b/alc/inprogext.h @@ -9,25 +9,6 @@ extern "C" { #endif -#ifndef ALC_SOFT_loopback_bformat -#define ALC_SOFT_loopback_bformat 1 -#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 -#define ALC_AMBISONIC_SCALING_SOFT 0x1998 -#define ALC_AMBISONIC_ORDER_SOFT 0x1999 -#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B - -#define ALC_BFORMAT3D_SOFT 0x1508 - -/* Ambisonic layouts */ -#define ALC_FUMA_SOFT 0x0000 -#define ALC_ACN_SOFT 0x0001 - -/* Ambisonic scalings (normalization) */ -/*#define ALC_FUMA_SOFT*/ -#define ALC_SN3D_SOFT 0x0001 -#define ALC_N3D_SOFT 0x0002 -#endif - #ifndef AL_SOFT_map_buffer #define AL_SOFT_map_buffer 1 typedef unsigned int ALbitfieldSOFT; @@ -47,36 +28,44 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A #endif #endif -#ifndef AL_SOFT_events -#define AL_SOFT_events 1 -#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x1220 -#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x1221 -#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x1222 -#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x1223 -#define AL_EVENT_TYPE_ERROR_SOFT 0x1224 -#define AL_EVENT_TYPE_PERFORMANCE_SOFT 0x1225 -#define AL_EVENT_TYPE_DEPRECATED_SOFT 0x1226 -#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x1227 -typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); +#ifndef AL_SOFT_bformat_hoa +#define AL_SOFT_bformat_hoa +#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D +#endif + +#ifndef AL_SOFT_convolution_reverb +#define AL_SOFT_convolution_reverb +#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 +#define AL_EFFECTSLOT_STATE_SOFT 0x199D +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids); #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids); #endif #endif -#ifndef AL_SOFT_effect_chain -#define AL_SOFT_effect_chain -#define AL_EFFECTSLOT_TARGET_SOFT 0xf000 +#ifndef AL_SOFT_hold_on_disconnect +#define AL_SOFT_hold_on_disconnect +#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB #endif + +/* Non-standard export. Not part of any extension. */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); + + +/* Functions from abandoned extensions. 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/alc/logging.h b/alc/logging.h deleted file mode 100644 index ec6023a5..00000000 --- a/alc/logging.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef LOGGING_H -#define LOGGING_H - -#include <stdio.h> - -#include "opthelpers.h" - - -#ifdef __GNUC__ -#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z)))) -#else -#define DECL_FORMAT(x, y, z) -#endif - - -extern FILE *gLogFile; - -void al_print(FILE *logfile, const char *fmt, ...) DECL_FORMAT(printf, 2,3); -#if !defined(_WIN32) -#define AL_PRINT fprintf -#else -#define AL_PRINT al_print -#endif - -#ifdef __ANDROID__ -#include <android/log.h> -#define LOG_ANDROID(T, ...) __android_log_print(T, "openal", "AL lib: " __VA_ARGS__) -#else -#define LOG_ANDROID(T, ...) ((void)0) -#endif - -enum LogLevel { - NoLog, - LogError, - LogWarning, - LogTrace, - LogRef -}; -extern LogLevel gLogLevel; - -#define TRACE(...) do { \ - if UNLIKELY(gLogLevel >= LogTrace) \ - AL_PRINT(gLogFile, "AL lib: (II) " __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \ -} while(0) - -#define WARN(...) do { \ - if UNLIKELY(gLogLevel >= LogWarning) \ - AL_PRINT(gLogFile, "AL lib: (WW) " __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \ -} while(0) - -#define ERR(...) do { \ - if UNLIKELY(gLogLevel >= LogError) \ - AL_PRINT(gLogFile, "AL lib: (EE) " __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \ -} while(0) - -#endif /* LOGGING_H */ diff --git a/alc/mastering.cpp b/alc/mastering.cpp deleted file mode 100644 index 46cc3134..00000000 --- a/alc/mastering.cpp +++ /dev/null @@ -1,468 +0,0 @@ - -#include "config.h" - -#include "mastering.h" - -#include <algorithm> -#include <cmath> -#include <cstddef> -#include <functional> -#include <iterator> -#include <limits> -#include <new> - -#include "AL/al.h" - -#include "almalloc.h" -#include "alnumeric.h" -#include "alu.h" -#include "opthelpers.h" - - -/* These structures assume BUFFERSIZE is a power of 2. */ -static_assert((BUFFERSIZE & (BUFFERSIZE-1)) == 0, "BUFFERSIZE is not a power of 2"); - -struct SlidingHold { - alignas(16) ALfloat mValues[BUFFERSIZE]; - ALuint mExpiries[BUFFERSIZE]; - ALuint mLowerIndex; - ALuint mUpperIndex; - ALuint mLength; -}; - - -namespace { - -using namespace std::placeholders; - -/* This sliding hold follows the input level with an instant attack and a - * fixed duration hold before an instant release to the next highest level. - * It is a sliding window maximum (descending maxima) implementation based on - * Richard Harter's ascending minima algorithm available at: - * - * http://www.richardhartersworld.com/cri/2001/slidingmin.html - */ -ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALuint i, const ALfloat in) -{ - static constexpr ALuint mask{BUFFERSIZE - 1}; - const ALuint length{Hold->mLength}; - ALfloat (&values)[BUFFERSIZE] = Hold->mValues; - ALuint (&expiries)[BUFFERSIZE] = Hold->mExpiries; - ALuint lowerIndex{Hold->mLowerIndex}; - ALuint upperIndex{Hold->mUpperIndex}; - - if(i >= expiries[upperIndex]) - upperIndex = (upperIndex + 1) & mask; - - if(in >= values[upperIndex]) - { - values[upperIndex] = in; - expiries[upperIndex] = i + length; - lowerIndex = upperIndex; - } - else - { - do { - do { - if(!(in >= values[lowerIndex])) - goto found_place; - } while(lowerIndex--); - lowerIndex = mask; - } while(1); - found_place: - - lowerIndex = (lowerIndex + 1) & mask; - values[lowerIndex] = in; - expiries[lowerIndex] = i + length; - } - - Hold->mLowerIndex = lowerIndex; - Hold->mUpperIndex = upperIndex; - - return values[upperIndex]; -} - -void ShiftSlidingHold(SlidingHold *Hold, const ALuint n) -{ - auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex; - auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex; - if(exp_last-exp_begin < 0) - { - std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - std::bind(std::minus<ALuint>{}, _1, n)); - exp_begin = std::begin(Hold->mExpiries); - } - std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<ALuint>{}, _1, n)); -} - - -/* Multichannel compression is linked via the absolute maximum of all - * channels. - */ -void LinkChannels(Compressor *Comp, const ALuint SamplesToDo, const FloatBufferLine *OutBuffer) -{ - const ALuint numChans{Comp->mNumChans}; - - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); - - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::fill(side_begin, side_begin+SamplesToDo, 0.0f); - - auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void - { - const ALfloat *RESTRICT buffer{al::assume_aligned<16>(input.data())}; - auto max_abs = std::bind(maxf, _1, std::bind(static_cast<float(&)(float)>(std::fabs), _2)); - std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); - }; - std::for_each(OutBuffer, OutBuffer+numChans, fill_max); -} - -/* This calculates the squared crest factor of the control signal for the - * basic automation of the attack/release times. As suggested by the paper, - * it uses an instantaneous squared peak detector and a squared RMS detector - * both with 200ms release times. - */ -static void CrestDetector(Compressor *Comp, const ALuint SamplesToDo) -{ - const ALfloat a_crest{Comp->mCrestCoeff}; - ALfloat y2_peak{Comp->mLastPeakSq}; - ALfloat y2_rms{Comp->mLastRmsSq}; - - ASSUME(SamplesToDo > 0); - - auto calc_crest = [&y2_rms,&y2_peak,a_crest](const ALfloat x_abs) noexcept -> ALfloat - { - const ALfloat 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); - return y2_peak / y2_rms; - }; - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest); - - Comp->mLastPeakSq = y2_peak; - Comp->mLastRmsSq = y2_rms; -} - -/* The side-chain starts with a simple peak detector (based on the absolute - * value of the incoming signal) and performs most of its operations in the - * log domain. - */ -void PeakDetector(Compressor *Comp, const ALuint SamplesToDo) -{ - ASSUME(SamplesToDo > 0); - - /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, - std::bind(static_cast<float(&)(float)>(std::log), std::bind(maxf, 0.000001f, _1))); -} - -/* An optional hold can be used to extend the peak detector so it can more - * solidly detect fast transients. This is best used when operating as a - * limiter. - */ -void PeakHoldDetector(Compressor *Comp, const ALuint SamplesToDo) -{ - ASSUME(SamplesToDo > 0); - - SlidingHold *hold{Comp->mHold}; - ALuint i{0}; - auto detect_peak = [&i,hold](const ALfloat x_abs) -> ALfloat - { - const ALfloat x_G{std::log(maxf(0.000001f, x_abs))}; - return UpdateSlidingHold(hold, i++, x_G); - }; - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak); - - ShiftSlidingHold(hold, SamplesToDo); -} - -/* This is the heart of the feed-forward compressor. It operates in the log - * domain (to better match human hearing) and can apply some basic automation - * to knee width, attack/release times, make-up/post gain, and clipping - * reduction. - */ -void GainCompressor(Compressor *Comp, const ALuint SamplesToDo) -{ - const bool autoKnee{Comp->mAuto.Knee}; - const bool autoAttack{Comp->mAuto.Attack}; - const bool autoRelease{Comp->mAuto.Release}; - const bool autoPostGain{Comp->mAuto.PostGain}; - const bool autoDeclip{Comp->mAuto.Declip}; - const ALuint lookAhead{Comp->mLookAhead}; - const ALfloat threshold{Comp->mThreshold}; - const ALfloat slope{Comp->mSlope}; - const ALfloat attack{Comp->mAttack}; - const ALfloat release{Comp->mRelease}; - const ALfloat c_est{Comp->mGainEstimate}; - const ALfloat a_adp{Comp->mAdaptCoeff}; - const ALfloat *crestFactor{Comp->mCrestFactor}; - ALfloat postGain{Comp->mPostGain}; - ALfloat knee{Comp->mKnee}; - ALfloat t_att{attack}; - ALfloat t_rel{release - attack}; - ALfloat a_att{std::exp(-1.0f / t_att)}; - ALfloat a_rel{std::exp(-1.0f / t_rel)}; - ALfloat y_1{Comp->mLastRelease}; - ALfloat y_L{Comp->mLastAttack}; - ALfloat c_dev{Comp->mLastGainDev}; - - ASSUME(SamplesToDo > 0); - - for(ALfloat &sideChain : al::span<float>{Comp->mSideChain, SamplesToDo}) - { - if(autoKnee) - knee = maxf(0.0f, 2.5f * (c_dev + c_est)); - const ALfloat knee_h{0.5f * knee}; - - /* This is the gain computer. It applies a static compression curve - * to the control signal. - */ - const ALfloat x_over{std::addressof(sideChain)[lookAhead] - threshold}; - const ALfloat y_G{ - (x_over <= -knee_h) ? 0.0f : - (std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) : - x_over - }; - - const ALfloat y2_crest{*(crestFactor++)}; - if(autoAttack) - { - t_att = 2.0f*attack/y2_crest; - a_att = std::exp(-1.0f / t_att); - } - if(autoRelease) - { - t_rel = 2.0f*release/y2_crest - t_att; - a_rel = std::exp(-1.0f / t_rel); - } - - /* Gain smoothing (ballistics) is done via a smooth decoupled peak - * detector. The attack time is subtracted from the release time - * above to compensate for the chained operating mode. - */ - const ALfloat x_L{-slope * y_G}; - y_1 = maxf(x_L, lerp(x_L, y_1, a_rel)); - y_L = lerp(y_1, y_L, a_att); - - /* 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); - - if(autoPostGain) - { - /* Clipping reduction is only viable when make-up gain is being - * automated. It modifies the deviation to further attenuate the - * control signal when clipping is detected. The adaptation time - * is sufficiently long enough to suppress further clipping at the - * same output level. - */ - if(autoDeclip) - c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est); - - postGain = -(c_dev + c_est); - } - - sideChain = std::exp(postGain - y_L); - } - - Comp->mLastRelease = y_1; - Comp->mLastAttack = y_L; - Comp->mLastGainDev = c_dev; -} - -/* Combined with the hold time, a look-ahead delay can improve handling of - * fast transients by allowing the envelope time to converge prior to - * reaching the offending impulse. This is best used when operating as a - * limiter. - */ -void SignalDelay(Compressor *Comp, const ALuint SamplesToDo, FloatBufferLine *OutBuffer) -{ - const ALuint numChans{Comp->mNumChans}; - const ALuint lookAhead{Comp->mLookAhead}; - - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); - ASSUME(lookAhead > 0); - - for(ALuint c{0};c < numChans;c++) - { - ALfloat *inout{al::assume_aligned<16>(OutBuffer[c].data())}; - ALfloat *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())}; - - auto inout_end = inout + SamplesToDo; - if LIKELY(SamplesToDo >= lookAhead) - { - auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); - std::swap_ranges(inout, delay_end, delaybuf); - } - else - { - auto delay_start = std::swap_ranges(inout, inout_end, delaybuf); - std::rotate(delaybuf, delay_start, delaybuf + lookAhead); - } - } -} - -} // namespace - -/* The compressor is initialized with the following settings: - * - * NumChans - Number of channels to process. - * SampleRate - Sample rate to process. - * AutoKnee - Whether to automate the knee width parameter. - * AutoAttack - Whether to automate the attack time parameter. - * AutoRelease - Whether to automate the release time parameter. - * AutoPostGain - Whether to automate the make-up (post) gain parameter. - * AutoDeclip - Whether to automate clipping reduction. Ignored when - * not automating make-up gain. - * LookAheadTime - Look-ahead time (in seconds). - * HoldTime - Peak hold-time (in seconds). - * PreGainDb - Gain applied before detection (in dB). - * PostGainDb - Make-up gain applied after compression (in dB). - * ThresholdDb - Triggering threshold (in dB). - * Ratio - Compression ratio (x:1). Set to INFINITY for true - * limiting. Ignored when automating knee width. - * KneeDb - Knee width (in dB). Ignored when automating knee - * width. - * AttackTimeMin - Attack time (in seconds). Acts as a maximum when - * automating attack time. - * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when - * automating release time. - */ -std::unique_ptr<Compressor> CompressorInit(const ALuint NumChans, const ALfloat SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease, - const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb, - const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime, - const ALfloat ReleaseTime) -{ - const auto lookAhead = static_cast<ALuint>( - clampf(std::round(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1)); - const auto hold = static_cast<ALuint>( - clampf(std::round(HoldTime*SampleRate), 0.0f, BUFFERSIZE-1)); - - size_t size{sizeof(Compressor)}; - if(lookAhead > 0) - { - size += sizeof(*Compressor::mDelay) * NumChans; - /* The sliding hold implementation doesn't handle a length of 1. A 1- - * sample hold is useless anyway, it would only ever give back what was - * just given to it. - */ - if(hold > 1) - size += sizeof(*Compressor::mHold); - } - - auto Comp = std::unique_ptr<Compressor>{new (al_calloc(16, size)) Compressor{}}; - Comp->mNumChans = NumChans; - Comp->mAuto.Knee = AutoKnee != AL_FALSE; - Comp->mAuto.Attack = AutoAttack != AL_FALSE; - Comp->mAuto.Release = AutoRelease != AL_FALSE; - Comp->mAuto.PostGain = AutoPostGain != AL_FALSE; - Comp->mAuto.Declip = AutoPostGain && AutoDeclip; - Comp->mLookAhead = lookAhead; - Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f); - Comp->mPostGain = PostGainDb * std::log(10.0f) / 20.0f; - Comp->mThreshold = ThresholdDb * std::log(10.0f) / 20.0f; - Comp->mSlope = 1.0f / maxf(1.0f, Ratio) - 1.0f; - Comp->mKnee = maxf(0.0f, KneeDb * std::log(10.0f) / 20.0f); - Comp->mAttack = maxf(1.0f, AttackTime * SampleRate); - Comp->mRelease = maxf(1.0f, ReleaseTime * SampleRate); - - /* Knee width automation actually treats the compressor as a limiter. By - * varying the knee width, it can effectively be seen as applying - * compression over a wide range of ratios. - */ - if(AutoKnee) - Comp->mSlope = -1.0f; - - if(lookAhead > 0) - { - if(hold > 1) - { - Comp->mHold = ::new (static_cast<void*>(Comp.get() + 1)) SlidingHold{}; - Comp->mHold->mValues[0] = -std::numeric_limits<float>::infinity(); - Comp->mHold->mExpiries[0] = hold; - Comp->mHold->mLength = hold; - Comp->mDelay = ::new (static_cast<void*>(Comp->mHold + 1)) FloatBufferLine[NumChans]; - } - else - { - Comp->mDelay = ::new (static_cast<void*>(Comp.get() + 1)) FloatBufferLine[NumChans]; - } - std::fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); - } - - Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms - Comp->mGainEstimate = Comp->mThreshold * -0.5f * Comp->mSlope; - Comp->mAdaptCoeff = std::exp(-1.0f / (2.0f * SampleRate)); // 2s - - return Comp; -} - -Compressor::~Compressor() -{ - if(mHold) - al::destroy_at(mHold); - mHold = nullptr; - if(mDelay) - al::destroy_n(mDelay, mNumChans); - mDelay = nullptr; -} - - -void Compressor::process(const ALuint SamplesToDo, FloatBufferLine *OutBuffer) -{ - const ALuint numChans{mNumChans}; - - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); - - const ALfloat preGain{mPreGain}; - if(preGain != 1.0f) - { - auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void - { - ALfloat *buffer{al::assume_aligned<16>(input.data())}; - std::transform(buffer, buffer+SamplesToDo, buffer, - std::bind(std::multiplies<float>{}, _1, preGain)); - }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); - } - - LinkChannels(this, SamplesToDo, OutBuffer); - - if(mAuto.Attack || mAuto.Release) - CrestDetector(this, SamplesToDo); - - if(mHold) - PeakHoldDetector(this, SamplesToDo); - else - PeakDetector(this, SamplesToDo); - - GainCompressor(this, SamplesToDo); - - if(mDelay) - SignalDelay(this, SamplesToDo, OutBuffer); - - const ALfloat (&sideChain)[BUFFERSIZE*2] = mSideChain; - auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void - { - ALfloat *buffer{al::assume_aligned<16>(input.data())}; - const ALfloat *gains{al::assume_aligned<16>(&sideChain[0])}; - std::transform(gains, gains+SamplesToDo, buffer, buffer, - std::bind(std::multiplies<float>{}, _1, _2)); - }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); - - auto side_begin = std::begin(mSideChain) + SamplesToDo; - std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain)); -} diff --git a/alc/mastering.h b/alc/mastering.h deleted file mode 100644 index 851381e9..00000000 --- a/alc/mastering.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef MASTERING_H -#define MASTERING_H - -#include <memory> - -#include "AL/al.h" - -/* For FloatBufferLine/BUFFERSIZE. */ -#include "alcmain.h" -#include "almalloc.h" - -struct SlidingHold; - - -/* General topology and basic automation was based on the following paper: - * - * D. Giannoulis, M. Massberg and J. D. Reiss, - * "Parameter Automation in a Dynamic Range Compressor," - * Journal of the Audio Engineering Society, v61 (10), Oct. 2013 - * - * Available (along with supplemental reading) at: - * - * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ - */ -struct Compressor { - ALuint mNumChans{0u}; - - struct { - bool Knee : 1; - bool Attack : 1; - bool Release : 1; - bool PostGain : 1; - bool Declip : 1; - } mAuto{}; - - ALuint mLookAhead{0}; - - ALfloat mPreGain{0.0f}; - ALfloat mPostGain{0.0f}; - - ALfloat mThreshold{0.0f}; - ALfloat mSlope{0.0f}; - ALfloat mKnee{0.0f}; - - ALfloat mAttack{0.0f}; - ALfloat mRelease{0.0f}; - - alignas(16) ALfloat mSideChain[2*BUFFERSIZE]{}; - alignas(16) ALfloat mCrestFactor[BUFFERSIZE]{}; - - SlidingHold *mHold{nullptr}; - FloatBufferLine *mDelay{nullptr}; - - ALfloat mCrestCoeff{0.0f}; - ALfloat mGainEstimate{0.0f}; - ALfloat mAdaptCoeff{0.0f}; - - ALfloat mLastPeakSq{0.0f}; - ALfloat mLastRmsSq{0.0f}; - ALfloat mLastRelease{0.0f}; - ALfloat mLastAttack{0.0f}; - ALfloat mLastGainDev{0.0f}; - - - ~Compressor(); - void process(const ALuint SamplesToDo, FloatBufferLine *OutBuffer); - ALsizei getLookAhead() const noexcept { return static_cast<ALsizei>(mLookAhead); } - - DEF_PLACE_NEWDEL() -}; - -/* The compressor is initialized with the following settings: - * - * NumChans - Number of channels to process. - * SampleRate - Sample rate to process. - * AutoKnee - Whether to automate the knee width parameter. - * AutoAttack - Whether to automate the attack time parameter. - * AutoRelease - Whether to automate the release time parameter. - * AutoPostGain - Whether to automate the make-up (post) gain parameter. - * AutoDeclip - Whether to automate clipping reduction. Ignored when - * not automating make-up gain. - * LookAheadTime - Look-ahead time (in seconds). - * HoldTime - Peak hold-time (in seconds). - * PreGainDb - Gain applied before detection (in dB). - * PostGainDb - Make-up gain applied after compression (in dB). - * ThresholdDb - Triggering threshold (in dB). - * Ratio - Compression ratio (x:1). Set to INFINIFTY for true - * limiting. Ignored when automating knee width. - * KneeDb - Knee width (in dB). Ignored when automating knee - * width. - * AttackTimeMin - Attack time (in seconds). Acts as a maximum when - * automating attack time. - * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when - * automating release time. - */ -std::unique_ptr<Compressor> CompressorInit(const ALuint NumChans, const ALfloat SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease, - const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb, - const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime, - const ALfloat ReleaseTime); - -#endif /* MASTERING_H */ diff --git a/alc/mixer/defs.h b/alc/mixer/defs.h deleted file mode 100644 index 1e5b40d8..00000000 --- a/alc/mixer/defs.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef MIXER_DEFS_H -#define MIXER_DEFS_H - -#include "AL/al.h" - -#include "alcmain.h" -#include "alspan.h" -#include "hrtf.h" - -union InterpState; -struct MixHrtfFilter; - - -enum InstSetType { - CTag, - SSETag, - SSE2Tag, - SSE3Tag, - SSE4Tag, - NEONTag -}; - -enum ResampleType { - CopyTag, - PointTag, - LerpTag, - CubicTag, - BSincTag, - FastBSincTag -}; - -template<ResampleType TypeTag, InstSetType InstTag> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, ALuint frac, - ALuint increment, const al::span<float> dst); - -template<InstSetType InstTag> -void Mix_(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos); -template<InstSetType InstTag> -void MixRow_(const al::span<float> OutBuffer, const al::span<const float> Gains, - const float *InSamples, const size_t InStride); - -template<InstSetType InstTag> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize); -template<InstSetType InstTag> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize); -template<InstSetType InstTag> -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const size_t BufferSize); - -/* Vectorized resampler helpers */ -inline void InitPosArrays(ALuint frac, ALuint increment, ALuint *frac_arr, ALuint *pos_arr, - size_t size) -{ - pos_arr[0] = 0; - frac_arr[0] = frac; - for(size_t i{1};i < size;i++) - { - const ALuint frac_tmp{frac_arr[i-1] + increment}; - pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS); - frac_arr[i] = frac_tmp&FRACTIONMASK; - } -} - -#endif /* MIXER_DEFS_H */ diff --git a/alc/mixer/hrtfbase.h b/alc/mixer/hrtfbase.h deleted file mode 100644 index 4a6eab50..00000000 --- a/alc/mixer/hrtfbase.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef MIXER_HRTFBASE_H -#define MIXER_HRTFBASE_H - -#include <algorithm> - -#include "alu.h" -#include "../hrtf.h" -#include "opthelpers.h" -#include "voice.h" - - -using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const ALuint irSize, const HrirArray &Coeffs, - const float left, const float right); - -template<ApplyCoeffsT ApplyCoeffs> -inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ - ASSUME(BufferSize > 0); - - const HrirArray &Coeffs = *hrtfparams->Coeffs; - const float gainstep{hrtfparams->GainStep}; - const float gain{hrtfparams->Gain}; - - ALsizei Delay[2]{ - HRTF_HISTORY_LENGTH - hrtfparams->Delay[0], - HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] }; - ASSUME(Delay[0] >= 0 && Delay[1] >= 0); - float stepcount{0.0f}; - for(size_t i{0u};i < BufferSize;++i) - { - const float g{gain + gainstep*stepcount}; - const float left{InSamples[Delay[0]++] * g}; - const float right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right); - - stepcount += 1.0f; - } - - hrtfparams->Gain = gain + gainstep*stepcount; -} - -template<ApplyCoeffsT ApplyCoeffs> -inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples, - const ALuint IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams, - const size_t BufferSize) -{ - const auto &OldCoeffs = oldparams->Coeffs; - const float oldGain{oldparams->Gain}; - const float oldGainStep{-oldGain / static_cast<float>(BufferSize)}; - const auto &NewCoeffs = *newparams->Coeffs; - const float newGainStep{newparams->GainStep}; - - ASSUME(BufferSize > 0); - - ALsizei Delay[2]{ - HRTF_HISTORY_LENGTH - oldparams->Delay[0], - HRTF_HISTORY_LENGTH - oldparams->Delay[1] }; - ASSUME(Delay[0] >= 0 && Delay[1] >= 0); - float stepcount{0.0f}; - for(size_t i{0u};i < BufferSize;++i) - { - const float g{oldGain + oldGainStep*stepcount}; - const float left{InSamples[Delay[0]++] * g}; - const float right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right); - - stepcount += 1.0f; - } - - Delay[0] = HRTF_HISTORY_LENGTH - newparams->Delay[0]; - Delay[1] = HRTF_HISTORY_LENGTH - newparams->Delay[1]; - ASSUME(Delay[0] >= 0 && Delay[1] >= 0); - stepcount = 0.0f; - for(size_t i{0u};i < BufferSize;++i) - { - const float g{newGainStep*stepcount}; - const float left{InSamples[Delay[0]++] * g}; - const float right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right); - - stepcount += 1.0f; - } - - newparams->Gain = newGainStep*stepcount; -} - -template<ApplyCoeffsT ApplyCoeffs> -inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *RESTRICT AccumSamples, - DirectHrtfState *State, const size_t BufferSize) -{ - ASSUME(BufferSize > 0); - - const ALuint IrSize{State->IrSize}; - - auto coeff_iter = State->Coeffs.begin(); - for(const FloatBufferLine &input : InSamples) - { - const auto &Coeffs = *(coeff_iter++); - for(size_t i{0u};i < BufferSize;++i) - { - const float insample{input[i]}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample); - } - } - for(size_t i{0u};i < BufferSize;++i) - LeftOut[i] += AccumSamples[i][0]; - for(size_t i{0u};i < BufferSize;++i) - RightOut[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, HRIR_LENGTH, AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); -} - -#endif /* MIXER_HRTFBASE_H */ diff --git a/alc/mixer/mixer_c.cpp b/alc/mixer/mixer_c.cpp deleted file mode 100644 index fad33746..00000000 --- a/alc/mixer/mixer_c.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "config.h" - -#include <cassert> - -#include <limits> - -#include "alcmain.h" -#include "alu.h" - -#include "defs.h" -#include "hrtfbase.h" - - -namespace { - -inline float do_point(const InterpState&, const float *RESTRICT vals, const ALuint) -{ return vals[0]; } -inline float do_lerp(const InterpState&, const float *RESTRICT vals, const ALuint frac) -{ return lerp(vals[0], vals[1], static_cast<float>(frac)*(1.0f/FRACTIONONE)); } -inline float do_cubic(const InterpState&, const float *RESTRICT vals, const ALuint frac) -{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast<float>(frac)*(1.0f/FRACTIONONE)); } -inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const ALuint frac) -{ - const size_t m{istate.bsinc.m}; - - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - const float *fil{istate.bsinc.filter + m*pi*4}; - const float *phd{fil + m}; - const float *scd{phd + m}; - const float *spd{scd + m}; - - // Apply the scale and phase interpolated filter. - float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f]; - return r; -} -inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const ALuint frac) -{ - const size_t m{istate.bsinc.m}; - - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - const float *fil{istate.bsinc.filter + m*pi*4}; - const float *phd{fil + m}; - - // Apply the phase interpolated filter. - float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + pf*phd[j_f]) * vals[j_f]; - return r; -} - -using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const ALuint); -template<SamplerT Sampler> -const float *DoResample(const InterpState *state, const float *RESTRICT src, ALuint frac, - ALuint increment, const al::span<float> dst) -{ - const InterpState istate{*state}; - auto proc_sample = [&src,&frac,istate,increment]() -> float - { - const float ret{Sampler(istate, src, frac)}; - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - - return ret; - }; - std::generate(dst.begin(), dst.end(), proc_sample); - - return dst.begin(); -} - -inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs, - const float left, const float right) -{ - ASSUME(IrSize >= 4); - for(ALuint c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } -} - -} // namespace - -template<> -const ALfloat *Resample_<CopyTag,CTag>(const InterpState*, const ALfloat *RESTRICT src, ALuint, - ALuint, const al::span<float> dst) -{ -#if defined(HAVE_SSE) || defined(HAVE_NEON) - /* Avoid copying the source data if it's aligned like the destination. */ - if((reinterpret_cast<intptr_t>(src)&15) == (reinterpret_cast<intptr_t>(dst.data())&15)) - return src; -#endif - std::copy_n(src, dst.size(), dst.begin()); - return dst.begin(); -} - -template<> -const ALfloat *Resample_<PointTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ return DoResample<do_point>(state, src, frac, increment, dst); } - -template<> -const ALfloat *Resample_<LerpTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ return DoResample<do_lerp>(state, src, frac, increment, dst); } - -template<> -const ALfloat *Resample_<CubicTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ return DoResample<do_cubic>(state, src-1, frac, increment, dst); } - -template<> -const ALfloat *Resample_<BSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ return DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst); } - -template<> -const ALfloat *Resample_<FastBSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ return DoResample<do_fastbsinc>(state, src-state->bsinc.l, frac, increment, dst); } - - -template<> -void MixHrtf_<CTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } - -template<> -void MixHrtfBlend_<CTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); -} - -template<> -void MixDirectHrtf_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const size_t BufferSize) -{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); } - - -template<> -void Mix_<CTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) -{ - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; - const bool reached_target{InSamples.size() >= Counter}; - const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end(); - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - auto in_iter = InSamples.begin(); - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - while(in_iter != min_end) - { - *(dst++) += *(in_iter++) * (gain + step*step_count); - step_count += 1.0f; - } - if(reached_target) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - while(in_iter != InSamples.end()) - *(dst++) += *(in_iter++) * gain; - } -} - -/* Basically the inverse of the above. Rather than one input going to multiple - * outputs (each with its own gain), it's multiple inputs (each with its own - * gain) going to one output. This applies one row (vs one column) of a matrix - * transform. And as the matrices are more or less static once set up, no - * stepping is necessary. - */ -template<> -void MixRow_<CTag>(const al::span<float> OutBuffer, const al::span<const float> Gains, - const float *InSamples, const size_t InStride) -{ - for(const float gain : Gains) - { - const float *RESTRICT input{InSamples}; - InSamples += InStride; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - auto do_mix = [gain](const float cur, const float src) noexcept -> float - { return cur + src*gain; }; - std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), do_mix); - } -} diff --git a/alc/mixer/mixer_neon.cpp b/alc/mixer/mixer_neon.cpp deleted file mode 100644 index 67bf9c71..00000000 --- a/alc/mixer/mixer_neon.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include "config.h" - -#include <arm_neon.h> - -#include <limits> - -#include "AL/al.h" -#include "AL/alc.h" -#include "alcmain.h" -#include "alu.h" -#include "hrtf.h" -#include "defs.h" -#include "hrtfbase.h" - - -namespace { - -inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs, - const float left, const float right) -{ - float32x4_t leftright4; - { - float32x2_t leftright2 = vdup_n_f32(0.0); - leftright2 = vset_lane_f32(left, leftright2, 0); - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } - - ASSUME(IrSize >= 4); - for(ALuint c{0};c < IrSize;c += 2) - { - float32x4_t vals = vld1q_f32(&Values[c][0]); - float32x4_t coefs = vld1q_f32(&Coeffs[c][0]); - - vals = vmlaq_f32(vals, coefs, leftright4); - - vst1q_f32(&Values[c][0], vals); - } -} - -} // namespace - -template<> -const ALfloat *Resample_<LerpTag,NEONTag>(const InterpState*, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ - const int32x4_t increment4 = vdupq_n_s32(static_cast<int>(increment*4)); - const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE); - const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK); - alignas(16) ALuint pos_[4], frac_[4]; - int32x4_t pos4, frac4; - - InitPosArrays(frac, increment, frac_, pos_, 4); - frac4 = vld1q_s32(reinterpret_cast<int*>(frac_)); - pos4 = vld1q_s32(reinterpret_cast<int*>(pos_)); - - auto dst_iter = dst.begin(); - const auto aligned_end = (dst.size()&~3u) + dst_iter; - while(dst_iter != aligned_end) - { - const int pos0{vgetq_lane_s32(pos4, 0)}; - const int pos1{vgetq_lane_s32(pos4, 1)}; - const int pos2{vgetq_lane_s32(pos4, 2)}; - const int pos3{vgetq_lane_s32(pos4, 3)}; - const float32x4_t val1{src[pos0], src[pos1], src[pos2], src[pos3]}; - const float32x4_t val2{src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]}; - - /* val1 + (val2-val1)*mu */ - const float32x4_t r0{vsubq_f32(val2, val1)}; - const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)}; - const float32x4_t out{vmlaq_f32(val1, mu, r0)}; - - vst1q_f32(dst_iter, out); - dst_iter += 4; - - frac4 = vaddq_s32(frac4, increment4); - pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS)); - frac4 = vandq_s32(frac4, fracMask4); - } - - if(dst_iter != dst.end()) - { - src += static_cast<ALuint>(vgetq_lane_s32(pos4, 0)); - frac = static_cast<ALuint>(vgetq_lane_s32(frac4, 0)); - - do { - *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE)); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } while(dst_iter != dst.end()); - } - return dst.begin(); -} - -template<> -const ALfloat *Resample_<BSincTag,NEONTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ - const float *const filter{state->bsinc.filter}; - const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)}; - const size_t m{state->bsinc.m}; - - src -= state->bsinc.l; - for(float &out_sample : dst) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - // Apply the scale and phase interpolated filter. - float32x4_t r4{vdupq_n_f32(0.0f)}; - { - const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; - const float *scd{phd + m}; - const float *spd{scd + m}; - size_t td{m >> 2}; - size_t j{0u}; - - do { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const float32x4_t f4 = vmlaq_f32( - vmlaq_f32(vld1q_f32(fil), sf4, vld1q_f32(scd)), - pf4, vmlaq_f32(vld1q_f32(phd), sf4, vld1q_f32(spd))); - fil += 4; scd += 4; phd += 4; spd += 4; - /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); - j += 4; - } while(--td); - } - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst.begin(); -} - -template<> -const ALfloat *Resample_<FastBSincTag,NEONTag>(const InterpState *state, - const ALfloat *RESTRICT src, ALuint frac, ALuint increment, const al::span<float> dst) -{ - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; - - src -= state->bsinc.l; - for(float &out_sample : dst) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - // Apply the phase interpolated filter. - 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}; - size_t td{m >> 2}; - size_t j{0u}; - - do { - /* f = fil + pf*phd */ - const float32x4_t f4 = vmlaq_f32(vld1q_f32(fil), pf4, vld1q_f32(phd)); - /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); - fil += 4; phd += 4; j += 4; - } while(--td); - } - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst.begin(); -} - - -template<> -void MixHrtf_<NEONTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } - -template<> -void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); -} - -template<> -void MixDirectHrtf_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const size_t BufferSize) -{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); } - - -template<> -void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) -{ - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; - const bool reached_target{InSamples.size() >= Counter}; - const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end(); - const auto aligned_end = minz(static_cast<uintptr_t>(min_end-InSamples.begin()+3) & ~3u, - InSamples.size()) + InSamples.begin(); - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - auto in_iter = InSamples.begin(); - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(ptrdiff_t todo{(min_end-in_iter) >> 2}) - { - const float32x4_t four4{vdupq_n_f32(4.0f)}; - const float32x4_t step4{vdupq_n_f32(step)}; - const float32x4_t gain4{vdupq_n_f32(gain)}; - float32x4_t step_count4{vsetq_lane_f32(0.0f, - vsetq_lane_f32(1.0f, - vsetq_lane_f32(2.0f, - vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3), - 2), 1), 0 - )}; - do { - const float32x4_t val4 = vld1q_f32(in_iter); - float32x4_t dry4 = vld1q_f32(dst); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(dst, dry4); - in_iter += 4; dst += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = vgetq_lane_f32(step_count4, 0); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - while(in_iter != min_end) - { - *(dst++) += *(in_iter++) * (gain + step*step_count); - step_count += 1.0f; - } - if(reached_target) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - while(in_iter != aligned_end) - *(dst++) += *(in_iter++) * gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(ptrdiff_t todo{(InSamples.end()-in_iter) >> 2}) - { - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(in_iter); - float32x4_t dry4 = vld1q_f32(dst); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(dst, dry4); - in_iter += 4; dst += 4; - } while(--todo); - } - while(in_iter != InSamples.end()) - *(dst++) += *(in_iter++) * gain; - } -} - -template<> -void MixRow_<NEONTag>(const al::span<float> OutBuffer, const al::span<const float> Gains, - const float *InSamples, const size_t InStride) -{ - for(const ALfloat gain : Gains) - { - const ALfloat *RESTRICT input{InSamples}; - InSamples += InStride; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - auto out_iter = OutBuffer.begin(); - if(size_t todo{OutBuffer.size() >> 2}) - { - const float32x4_t gain4{vdupq_n_f32(gain)}; - do { - const float32x4_t val4 = vld1q_f32(input); - float32x4_t dry4 = vld1q_f32(out_iter); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(out_iter, dry4); - out_iter += 4; input += 4; - } while(--todo); - } - - auto do_mix = [gain](const float cur, const float src) noexcept -> float - { return cur + src*gain; }; - std::transform(out_iter, OutBuffer.end(), input, out_iter, do_mix); - } -} diff --git a/alc/mixer/mixer_sse.cpp b/alc/mixer/mixer_sse.cpp deleted file mode 100644 index aaf37df6..00000000 --- a/alc/mixer/mixer_sse.cpp +++ /dev/null @@ -1,297 +0,0 @@ -#include "config.h" - -#include <xmmintrin.h> - -#include <limits> - -#include "AL/al.h" -#include "AL/alc.h" -#include "alcmain.h" - -#include "alu.h" -#include "defs.h" -#include "hrtfbase.h" - - -namespace { - -inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs, - const float left, const float right) -{ - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - - ASSUME(IrSize >= 4); - /* This isn't technically correct to test alignment, but it's true for - * systems that support SSE, which is the only one that needs to know the - * alignment of Values (which alternates between 8- and 16-byte aligned). - */ - if(reinterpret_cast<intptr_t>(Values)&0x8) - { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))}; - imp0 = _mm_mul_ps(lrlr, coeffs); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals); - ALuint i{1}; - for(;i < IrSize-1;i += 2) - { - coeffs = _mm_load_ps(&Coeffs[i+1][0]); - vals = _mm_load_ps(&Values[i][0]); - imp1 = _mm_mul_ps(lrlr, coeffs); - imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); - vals = _mm_add_ps(imp0, vals); - _mm_store_ps(&Values[i][0], vals); - imp0 = imp1; - } - vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0])); - imp0 = _mm_movehl_ps(imp0, imp0); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); - } - else - { - for(ALuint i{0};i < IrSize;i += 2) - { - __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; - __m128 vals{_mm_load_ps(&Values[i][0])}; - vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs)); - _mm_store_ps(&Values[i][0], vals); - } - } -} - -} // namespace - -template<> -const ALfloat *Resample_<BSincTag,SSETag>(const InterpState *state, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ - const float *const filter{state->bsinc.filter}; - const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; - const size_t m{state->bsinc.m}; - - src -= state->bsinc.l; - for(float &out_sample : dst) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - // Apply the scale and phase interpolated filter. - __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}; - size_t td{m >> 2}; - size_t j{0u}; - -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - do { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __m128 f4 = MLA4( - MLA4(_mm_load_ps(fil), sf4, _mm_load_ps(scd)), - pf4, MLA4(_mm_load_ps(phd), sf4, _mm_load_ps(spd))); - fil += 4; scd += 4; phd += 4; spd += 4; - /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); - j += 4; - } while(--td); -#undef MLA4 - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - out_sample = _mm_cvtss_f32(r4); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst.begin(); -} - -template<> -const ALfloat *Resample_<FastBSincTag,SSETag>(const InterpState *state, - const ALfloat *RESTRICT src, ALuint frac, ALuint increment, const al::span<float> dst) -{ - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; - - src -= state->bsinc.l; - for(float &out_sample : dst) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALuint pi{frac >> FRAC_PHASE_BITDIFF}; - const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * - (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - // Apply the phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; - { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; - size_t td{m >> 2}; - size_t j{0u}; - -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - do { - /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(fil), pf4, _mm_load_ps(phd)); - /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); - fil += 4; phd += 4; j += 4; - } while(--td); -#undef MLA4 - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - out_sample = _mm_cvtss_f32(r4); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst.begin(); -} - - -template<> -void MixHrtf_<SSETag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } - -template<> -void MixHrtfBlend_<SSETag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); -} - -template<> -void MixDirectHrtf_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const size_t BufferSize) -{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); } - - -template<> -void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) -{ - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; - const bool reached_target{InSamples.size() >= Counter}; - const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end(); - const auto aligned_end = minz(static_cast<uintptr_t>(min_end-InSamples.begin()+3) & ~3u, - InSamples.size()) + InSamples.begin(); - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - auto in_iter = InSamples.begin(); - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(ptrdiff_t todo{(min_end-in_iter) >> 2}) - { - const __m128 four4{_mm_set1_ps(4.0f)}; - const __m128 step4{_mm_set1_ps(step)}; - const __m128 gain4{_mm_set1_ps(gain)}; - __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; - do { - const __m128 val4{_mm_load_ps(in_iter)}; - __m128 dry4{_mm_load_ps(dst)}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); -#undef MLA4 - _mm_store_ps(dst, dry4); - step_count4 = _mm_add_ps(step_count4, four4); - in_iter += 4; dst += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = _mm_cvtss_f32(step_count4); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - while(in_iter != min_end) - { - *(dst++) += *(in_iter++) * (gain + step*step_count); - step_count += 1.0f; - } - if(reached_target) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - while(in_iter != aligned_end) - *(dst++) += *(in_iter++) * gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(ptrdiff_t todo{(InSamples.end()-in_iter) >> 2}) - { - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(in_iter)}; - __m128 dry4{_mm_load_ps(dst)}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(dst, dry4); - in_iter += 4; dst += 4; - } while(--todo); - } - while(in_iter != InSamples.end()) - *(dst++) += *(in_iter++) * gain; - } -} - -template<> -void MixRow_<SSETag>(const al::span<float> OutBuffer, const al::span<const float> Gains, - const float *InSamples, const size_t InStride) -{ - for(const float gain : Gains) - { - const float *RESTRICT input{InSamples}; - InSamples += InStride; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - auto out_iter = OutBuffer.begin(); - if(size_t todo{OutBuffer.size() >> 2}) - { - const __m128 gain4 = _mm_set1_ps(gain); - do { - const __m128 val4{_mm_load_ps(input)}; - __m128 dry4{_mm_load_ps(out_iter)}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(out_iter, dry4); - out_iter += 4; input += 4; - } while(--todo); - } - - auto do_mix = [gain](const float cur, const float src) noexcept -> float - { return cur + src*gain; }; - std::transform(out_iter, OutBuffer.end(), input, out_iter, do_mix); - } -} diff --git a/alc/mixer/mixer_sse2.cpp b/alc/mixer/mixer_sse2.cpp deleted file mode 100644 index 897cd1f7..00000000 --- a/alc/mixer/mixer_sse2.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2014 by Timothy Arceri <[email protected]>. - * 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 <xmmintrin.h> -#include <emmintrin.h> - -#include "alu.h" -#include "defs.h" - - -template<> -const ALfloat *Resample_<LerpTag,SSE2Tag>(const InterpState*, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ - const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - alignas(16) ALuint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]), - static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))}; - __m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]), - static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))}; - - auto dst_iter = dst.begin(); - const auto aligned_end = (dst.size()&~3u) + dst_iter; - while(dst_iter != aligned_end) - { - const int pos0{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)))}; - const int pos1{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)))}; - const int pos2{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)))}; - const int pos3{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)))}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; - - /* val1 + (val2-val1)*mu */ - const __m128 r0{_mm_sub_ps(val2, val1)}; - const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; - const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - - _mm_store_ps(dst_iter, out); - dst_iter += 4; - - frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); - frac4 = _mm_and_si128(frac4, fracMask4); - } - - if(dst_iter != dst.end()) - { - src += static_cast<ALuint>(_mm_cvtsi128_si32(pos4)); - frac = static_cast<ALuint>(_mm_cvtsi128_si32(frac4)); - - do { - *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE)); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } while(dst_iter != dst.end()); - } - return dst.begin(); -} diff --git a/alc/mixer/mixer_sse3.cpp b/alc/mixer/mixer_sse3.cpp deleted file mode 100644 index e69de29b..00000000 --- a/alc/mixer/mixer_sse3.cpp +++ /dev/null diff --git a/alc/mixer/mixer_sse41.cpp b/alc/mixer/mixer_sse41.cpp deleted file mode 100644 index cfa21e99..00000000 --- a/alc/mixer/mixer_sse41.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2014 by Timothy Arceri <[email protected]>. - * 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 <xmmintrin.h> -#include <emmintrin.h> -#include <smmintrin.h> - -#include "alu.h" -#include "defs.h" - - -template<> -const ALfloat *Resample_<LerpTag,SSE4Tag>(const InterpState*, const ALfloat *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst) -{ - const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - alignas(16) ALuint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]), - static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))}; - __m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]), - static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))}; - - auto dst_iter = dst.begin(); - const auto aligned_end = (dst.size()&~3u) + dst_iter; - while(dst_iter != aligned_end) - { - const int pos0{_mm_extract_epi32(pos4, 0)}; - const int pos1{_mm_extract_epi32(pos4, 1)}; - const int pos2{_mm_extract_epi32(pos4, 2)}; - const int pos3{_mm_extract_epi32(pos4, 3)}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; - - /* val1 + (val2-val1)*mu */ - const __m128 r0{_mm_sub_ps(val2, val1)}; - const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; - const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - - _mm_store_ps(dst_iter, out); - dst_iter += 4; - - frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); - frac4 = _mm_and_si128(frac4, fracMask4); - } - - if(dst_iter != dst.end()) - { - /* NOTE: These four elements represent the position *after* the last - * four samples, so the lowest element is the next position to - * resample. - */ - src += static_cast<ALuint>(_mm_cvtsi128_si32(pos4)); - frac = static_cast<ALuint>(_mm_cvtsi128_si32(frac4)); - - do { - *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE)); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } while(dst_iter != dst.end()); - } - return dst.begin(); -} diff --git a/alc/panning.cpp b/alc/panning.cpp index e85222bd..d118f99c 100644 --- a/alc/panning.cpp +++ b/alc/panning.cpp @@ -22,6 +22,7 @@ #include <algorithm> #include <array> +#include <cassert> #include <chrono> #include <cmath> #include <cstdio> @@ -38,33 +39,27 @@ #include "AL/alext.h" #include "al/auxeffectslot.h" -#include "alcmain.h" +#include "albit.h" #include "alconfig.h" +#include "alc/context.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" -#include "ambdec.h" -#include "ambidefs.h" -#include "bformatdec.h" -#include "bs2b.h" -#include "devformat.h" -#include "hrtf.h" -#include "logging.h" -#include "math_defs.h" +#include "core/ambdec.h" +#include "core/ambidefs.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/devformat.h" +#include "core/front_stablizer.h" +#include "core/hrtf.h" +#include "core/logging.h" +#include "core/uhjfilter.h" +#include "device.h" #include "opthelpers.h" -#include "uhjfilter.h" - - -constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromN3D; -constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromSN3D; -constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromFuMa; -constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::FromFuMa; -constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::FromACN; -constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> AmbiIndex::From2D; -constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::From3D; namespace { @@ -87,31 +82,30 @@ inline const char *GetLabelFromChannel(Channel channel) case SideLeft: return "side-left"; case SideRight: return "side-right"; - case UpperFrontLeft: return "upper-front-left"; - case UpperFrontRight: return "upper-front-right"; - case UpperBackLeft: return "upper-back-left"; - case UpperBackRight: return "upper-back-right"; - case LowerFrontLeft: return "lower-front-left"; - case LowerFrontRight: return "lower-front-right"; - case LowerBackLeft: return "lower-back-left"; - case LowerBackRight: return "lower-back-right"; - - case Aux0: return "aux-0"; - case Aux1: return "aux-1"; - case Aux2: return "aux-2"; - case Aux3: return "aux-3"; - case Aux4: return "aux-4"; - case Aux5: return "aux-5"; - case Aux6: return "aux-6"; - case Aux7: return "aux-7"; - case Aux8: return "aux-8"; - case Aux9: return "aux-9"; - case Aux10: return "aux-10"; - case Aux11: return "aux-11"; - case Aux12: return "aux-12"; - case Aux13: return "aux-13"; - case Aux14: return "aux-14"; - case Aux15: return "aux-15"; + case TopFrontLeft: return "top-front-left"; + case TopFrontCenter: return "top-front-center"; + case TopFrontRight: return "top-front-right"; + case TopCenter: return "top-center"; + case TopBackLeft: return "top-back-left"; + case TopBackCenter: return "top-back-center"; + case TopBackRight: return "top-back-right"; + + case Aux0: return "Aux0"; + case Aux1: return "Aux1"; + case Aux2: return "Aux2"; + case Aux3: return "Aux3"; + case Aux4: return "Aux4"; + case Aux5: return "Aux5"; + case Aux6: return "Aux6"; + case Aux7: return "Aux7"; + case Aux8: return "Aux8"; + case Aux9: return "Aux9"; + case Aux10: return "Aux10"; + case Aux11: return "Aux11"; + case Aux12: return "Aux12"; + case Aux13: return "Aux13"; + case Aux14: return "Aux14"; + case Aux15: return "Aux15"; case MaxChannels: break; } @@ -119,17 +113,31 @@ inline const char *GetLabelFromChannel(Channel channel) } -void AllocChannels(ALCdevice *device, const ALuint main_chans, const ALuint real_chans) +std::unique_ptr<FrontStablizer> CreateStablizer(const size_t outchans, const uint srate) { - TRACE("Channel config, Main: %u, Real: %u\n", main_chans, real_chans); + auto stablizer = FrontStablizer::Create(outchans); + + /* Initialize band-splitting filter for the mid signal, with a crossover at + * 5khz (could be higher). + */ + stablizer->MidFilter.init(5000.0f / static_cast<float>(srate)); + for(auto &filter : stablizer->ChannelFilters) + filter = stablizer->MidFilter; + + return stablizer; +} + +void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans) +{ + TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans); /* Allocate extra channels for any post-filter output. */ - const ALuint num_chans{main_chans + real_chans}; + const size_t num_chans{main_chans + real_chans}; - TRACE("Allocating %u channels, %zu bytes\n", num_chans, + TRACE("Allocating %zu channels, %zu bytes\n", num_chans, num_chans*sizeof(device->MixBuffer[0])); device->MixBuffer.resize(num_chans); - al::span<FloatBufferLine> buffer{device->MixBuffer.data(), device->MixBuffer.size()}; + al::span<FloatBufferLine> buffer{device->MixBuffer}; device->Dry.Buffer = buffer.first(main_chans); buffer = buffer.subspan(main_chans); @@ -143,168 +151,122 @@ void AllocChannels(ALCdevice *device, const ALuint main_chans, const ALuint real } -struct ChannelMap { - Channel ChanName; - ALfloat Config[MAX_AMBI2D_CHANNELS]; +using ChannelCoeffs = std::array<float,MaxAmbiChannels>; +enum DecoderMode : bool { + SingleBand = false, + DualBand = true }; -bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALuint (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - auto map_spkr = [device](const AmbDecConf::SpeakerConf &speaker) -> ALuint - { - /* NOTE: AmbDec does not define any standard speaker names, however - * for this to work we have to by able to find the output channel - * the speaker definition corresponds to. Therefore, OpenAL Soft - * requires these channel labels to be recognized: - * - * LF = Front left - * RF = Front right - * LS = Side left - * RS = Side right - * LB = Back left - * RB = Back right - * CE = Front center - * CB = Back center - * - * Additionally, surround51 will acknowledge back speakers for side - * channels, and surround51rear will acknowledge side speakers for - * back channels, to avoid issues with an ambdec expecting 5.1 to - * use the side channels when the device is configured for back, - * and vice-versa. - */ - Channel ch{}; - if(speaker.Name == "LF") - ch = FrontLeft; - else if(speaker.Name == "RF") - ch = FrontRight; - else if(speaker.Name == "CE") - ch = FrontCenter; - else if(speaker.Name == "LS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackLeft; - else - ch = SideLeft; - } - else if(speaker.Name == "RS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackRight; - else - ch = SideRight; - } - else if(speaker.Name == "LB") - { - if(device->FmtChans == DevFmtX51) - ch = SideLeft; - else - ch = BackLeft; - } - else if(speaker.Name == "RB") - { - if(device->FmtChans == DevFmtX51) - ch = SideRight; - else - ch = BackRight; - } - else if(speaker.Name == "CB") - ch = BackCenter; - else - { - const char *name{speaker.Name.c_str()}; - unsigned int n; - char c; +template<DecoderMode Mode, size_t N> +struct DecoderConfig; + +template<size_t N> +struct DecoderConfig<SingleBand, N> { + uint8_t mOrder{}; + bool mIs3D{}; + std::array<Channel,N> mChannels{}; + DevAmbiScaling mScaling{}; + std::array<float,MaxAmbiOrder+1> mOrderGain{}; + std::array<ChannelCoeffs,N> mCoeffs{}; +}; - if(sscanf(name, "AUX%u%c", &n, &c) == 1 && n < 16) - ch = static_cast<Channel>(Aux0+n); - else - { - ERR("AmbDec speaker label \"%s\" not recognized\n", name); - return INVALID_CHANNEL_INDEX; - } - } - const ALuint 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.begin(), conf->Speakers.end(), std::begin(speakermap), map_spkr); - /* Return success if no invalid entries are found. */ - auto spkrmap_end = std::begin(speakermap) + conf->Speakers.size(); - return std::find(std::begin(speakermap), spkrmap_end, INVALID_CHANNEL_INDEX) == spkrmap_end; -} +template<size_t N> +struct DecoderConfig<DualBand, N> { + uint8_t mOrder{}; + bool mIs3D{}; + std::array<Channel,N> mChannels{}; + DevAmbiScaling mScaling{}; + std::array<float,MaxAmbiOrder+1> mOrderGain{}; + std::array<ChannelCoeffs,N> mCoeffs{}; + std::array<float,MaxAmbiOrder+1> mOrderGainLF{}; + std::array<ChannelCoeffs,N> mCoeffsLF{}; +}; + +template<> +struct DecoderConfig<DualBand, 0> { + uint8_t mOrder{}; + bool mIs3D{}; + al::span<const Channel> mChannels; + DevAmbiScaling mScaling{}; + al::span<const float> mOrderGain; + al::span<const ChannelCoeffs> mCoeffs; + al::span<const float> mOrderGainLF; + al::span<const ChannelCoeffs> mCoeffsLF; + + template<size_t N> + DecoderConfig& operator=(const DecoderConfig<SingleBand,N> &rhs) noexcept + { + mOrder = rhs.mOrder; + mIs3D = rhs.mIs3D; + mChannels = rhs.mChannels; + mScaling = rhs.mScaling; + mOrderGain = rhs.mOrderGain; + mCoeffs = rhs.mCoeffs; + mOrderGainLF = {}; + mCoeffsLF = {}; + return *this; + } + template<size_t N> + DecoderConfig& operator=(const DecoderConfig<DualBand,N> &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; + } -constexpr ChannelMap MonoCfg[1] = { - { FrontCenter, { 1.0f } }, -}, StereoCfg[2] = { - { FrontLeft, { 5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f } }, - { FrontRight, { 5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f } }, -}, QuadCfg[4] = { - { BackLeft, { 3.53553391e-1f, 2.04124145e-1f, -2.04124145e-1f } }, - { FrontLeft, { 3.53553391e-1f, 2.04124145e-1f, 2.04124145e-1f } }, - { FrontRight, { 3.53553391e-1f, -2.04124145e-1f, 2.04124145e-1f } }, - { BackRight, { 3.53553391e-1f, -2.04124145e-1f, -2.04124145e-1f } }, -}, X51SideCfg[4] = { - { SideLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } }, - { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } }, - { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } }, - { SideRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } }, -}, X51RearCfg[4] = { - { BackLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } }, - { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } }, - { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } }, - { BackRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } }, -}, X61Cfg[6] = { - { SideLeft, { 2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f } }, - { FrontLeft, { 1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f } }, - { FrontRight, { 1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f } }, - { SideRight, { 2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f } }, - { BackCenter, { 2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f } }, -}, X71Cfg[6] = { - { BackLeft, { 2.04124145e-1f, 1.08880247e-1f, -1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } }, - { SideLeft, { 2.04124145e-1f, 2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, -3.73460789e-2f, 0.00000000e+0f } }, - { FrontLeft, { 2.04124145e-1f, 1.08880247e-1f, 1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } }, - { FrontRight, { 2.04124145e-1f, -1.08880247e-1f, 1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } }, - { SideRight, { 2.04124145e-1f, -2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, 3.73460789e-2f, 0.00000000e+0f } }, - { BackRight, { 2.04124145e-1f, -1.08880247e-1f, -1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } }, + explicit operator bool() const noexcept { return !mChannels.empty(); } }; +using DecoderView = DecoderConfig<DualBand, 0>; -void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALuint order, - const al::span<const ALuint,MAX_AMBI_ORDER+1> chans_per_order) + +void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d) { + static const uint chans_per_order2d[MaxAmbiOrder+1]{ 1, 2, 2, 2 }; + static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 }; + /* NFC is only used when AvgSpeakerDist is greater than 0. */ - const char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "nfc", 0) || !(ctrl_dist > 0.0f)) + if(!device->getConfigValueBool("decoder", "nfc", false) || !(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); - auto iter = std::copy(chans_per_order.begin(), chans_per_order.begin()+order+1, + const float w1{SpeedOfSoundMetersPerSec / + (device->AvgSpeakerDist * static_cast<float>(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 ALuint (&speakermap)[MAX_OUTPUT_CHANNELS]) +void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels, + const al::span<const float,MAX_OUTPUT_CHANNELS> dists) { - auto get_max = std::bind(maxf, _1, - std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)); - const ALfloat maxdist{ - std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), float{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", true) || !(maxdist > 0.0f)) return; - const auto distSampleScale = static_cast<ALfloat>(device->Frequency)/SPEEDOFSOUNDMETRESPERSEC; - const auto ChanDelay = device->ChannelDelay.as_span(); + const auto distSampleScale = static_cast<float>(device->Frequency) / SpeedOfSoundMetersPerSec; + std::vector<DistanceComp::ChanData> ChanDelay; + ChanDelay.reserve(device->RealOut.Buffer.size()); size_t total{0u}; - for(size_t i{0u};i < conf->Speakers.size();i++) + for(size_t chidx{0};chidx < channels.size();++chidx) { - const AmbDecConf::SpeakerConf &speaker = conf->Speakers[i]; - const ALuint chan{speakermap[i]}; + const Channel ch{channels[chidx]}; + const uint idx{device->RealOut.ChannelIndex[ch]}; + if(idx == InvalidChannelIndex) + 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 @@ -312,259 +274,495 @@ 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. */ - ALfloat delay{std::floor((maxdist - speaker.Distance)*distSampleScale + 0.5f)}; - if(delay > ALfloat{MAX_DELAY_LENGTH-1}) + float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; + if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for speaker \"%s\" exceeds buffer length (%f > %d)\n", - speaker.Name.c_str(), delay, MAX_DELAY_LENGTH-1); - delay = ALfloat{MAX_DELAY_LENGTH-1}; + ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, + GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); + delay = float{DistanceComp::MaxDelay-1}; } - ChanDelay[chan].Length = static_cast<ALuint>(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<uint>(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) { - device->ChannelDelay.setSampleCount(total); - ChanDelay[0].Buffer = device->ChannelDelay.getSamples(); - auto set_bufptr = [](const DistanceComp::DistData &last, const DistanceComp::DistData &cur) -> DistanceComp::DistData + auto chandelays = DistanceComp::Create(total); + + ChanDelay[0].Buffer = chandelays->mSamples.data(); + auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur) + -> DistanceComp::ChanData { - DistanceComp::DistData ret{cur}; + DistanceComp::ChanData ret{cur}; ret.Buffer = last.Buffer + RoundUp(last.Length, 4); return ret; }; - std::partial_sum(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); } } -auto GetAmbiScales(AmbiNorm scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>& +inline auto& GetAmbiScales(DevAmbiScaling scaletype) noexcept { - if(scaletype == AmbiNorm::FuMa) return AmbiScale::FromFuMa; - if(scaletype == AmbiNorm::SN3D) return AmbiScale::FromSN3D; - return AmbiScale::FromN3D; + if(scaletype == DevAmbiScaling::FuMa) return AmbiScale::FromFuMa(); + if(scaletype == DevAmbiScaling::SN3D) return AmbiScale::FromSN3D(); + return AmbiScale::FromN3D(); } -auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI_CHANNELS>& +inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa; - return AmbiIndex::FromACN; + if(layouttype == DevAmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); } -void InitPanning(ALCdevice *device) +DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, + DecoderConfig<DualBand, MAX_OUTPUT_CHANNELS> &decoder) { - al::span<const ChannelMap> chanmap; - ALuint coeffcount{}; - - switch(device->FmtChans) - { - case DevFmtMono: - chanmap = MonoCfg; - coeffcount = 1; - break; + DecoderView ret{}; - case DevFmtStereo: - chanmap = StereoCfg; - coeffcount = 3; - break; - - case DevFmtQuad: - chanmap = QuadCfg; - coeffcount = 3; - break; - - case DevFmtX51: - chanmap = X51SideCfg; - coeffcount = 5; - break; - - case DevFmtX51Rear: - chanmap = X51RearCfg; - coeffcount = 5; - break; + decoder.mOrder = (conf->ChanMask > Ambi3OrderMask) ? uint8_t{4} : + (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : + (conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1}; + decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0; - case DevFmtX61: - chanmap = X61Cfg; - coeffcount = 5; - break; + switch(conf->CoeffScale) + { + case AmbDecScale::Unset: ASSUME(false); break; + case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break; + case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break; + case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; + } - case DevFmtX71: - chanmap = X71Cfg; - coeffcount = 7; - break; + std::copy_n(std::begin(conf->HFOrderGain), + std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)), + std::begin(decoder.mOrderGain)); + std::copy_n(std::begin(conf->LFOrderGain), + std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), + std::begin(decoder.mOrderGainLF)); + + const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) + : Ambi2DChannelsFromOrder(decoder.mOrder); + const auto idx_map = decoder.mIs3D ? AmbiIndex::FromACN().data() + : AmbiIndex::FromACN2D().data(); + const auto hfmatrix = conf->HFMatrix; + const auto lfmatrix = conf->LFMatrix; + + uint chan_count{0}; + using const_speaker_span = al::span<const AmbDecConf::SpeakerConf>; + 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 + * LFT = Top front left + * RFT = Top front right + * LBT = Top back left + * RBT = Top back right + * + * Additionally, surround51 will acknowledge back speakers for side + * channels, to avoid issues with an ambdec expecting 5.1 to use the + * 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 if(speaker.Name == "LFT") + ch = TopFrontLeft; + else if(speaker.Name == "RFT") + ch = TopFrontRight; + else if(speaker.Name == "LBT") + ch = TopBackLeft; + else if(speaker.Name == "RBT") + ch = TopBackRight; + else + { + int idx{}; + char c{}; + if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 + || idx >= MaxChannels-Aux0) + { + ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + continue; + } + ch = static_cast<Channel>(Aux0+idx); + } - case DevFmtAmbi3D: - break; + decoder.mChannels[chan_count] = ch; + for(size_t dst{0};dst < num_coeffs;++dst) + { + const size_t src{idx_map[dst]}; + decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; + } + if(conf->FreqBands > 1) + { + for(size_t dst{0};dst < num_coeffs;++dst) + { + const size_t src{idx_map[dst]}; + decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; + } + } + ++chan_count; } - if(device->FmtChans == DevFmtAmbi3D) + if(chan_count > 0) { - const char *devname{device->DeviceName.c_str()}; - const std::array<uint8_t,MAX_AMBI_CHANNELS> &acnmap = GetAmbiLayout(device->mAmbiLayout); - const std::array<float,MAX_AMBI_CHANNELS> &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, static_cast<ALuint>(count), 0); - - ALfloat nfc_delay{ConfigValueFloat(devname, "decoder", "nfc-ref-delay").value_or(0.0f)}; - if(nfc_delay > 0.0f) + 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) { - static const ALuint chans_per_order[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; - InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC, device->mAmbiOrder, - chans_per_order); + ret.mOrderGainLF = decoder.mOrderGainLF; + ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count}; } } - else + return ret; +} + +constexpr DecoderConfig<SingleBand, 1> MonoConfig{ + 0, false, {{FrontCenter}}, + DevAmbiScaling::N3D, + {{1.0f}}, + {{ {{1.0f}} }} +}; +constexpr DecoderConfig<SingleBand, 2> StereoConfig{ + 1, false, {{FrontLeft, FrontRight}}, + DevAmbiScaling::N3D, + {{1.0f, 1.0f}}, + {{ + {{5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f}}, + {{5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f}}, + }} +}; +constexpr DecoderConfig<DualBand, 4> QuadConfig{ + 1, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{1.41421356e+0f, 1.00000000e+0f}}, + {{ + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, + }} +}; +constexpr DecoderConfig<DualBand, 5> X51Config{ + 2, false, {{SideLeft, FrontLeft, FrontCenter, FrontRight, SideRight}}, + DevAmbiScaling::FuMa, + /*HF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{5.67316000e-1f, 4.22920000e-1f, -3.15495000e-1f, -6.34490000e-2f, -2.92380000e-2f}}, + {{3.68584000e-1f, 2.72349000e-1f, 3.21616000e-1f, 1.92645000e-1f, 4.82600000e-2f}}, + {{1.83579000e-1f, 0.00000000e+0f, 1.99588000e-1f, 0.00000000e+0f, 9.62820000e-2f}}, + {{3.68584000e-1f, -2.72349000e-1f, 3.21616000e-1f, -1.92645000e-1f, 4.82600000e-2f}}, + {{5.67316000e-1f, -4.22920000e-1f, -3.15495000e-1f, 6.34490000e-2f, -2.92380000e-2f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{4.90109850e-1f, 3.77305010e-1f, -3.73106990e-1f, -1.25914530e-1f, 1.45133000e-2f}}, + {{1.49085730e-1f, 3.03561680e-1f, 1.53290060e-1f, 2.45112480e-1f, -1.50753130e-1f}}, + {{1.37654920e-1f, 0.00000000e+0f, 4.49417940e-1f, 0.00000000e+0f, 2.57844070e-1f}}, + {{1.49085730e-1f, -3.03561680e-1f, 1.53290060e-1f, -2.45112480e-1f, -1.50753130e-1f}}, + {{4.90109850e-1f, -3.77305010e-1f, -3.73106990e-1f, 1.25914530e-1f, 1.45133000e-2f}}, + }} +}; +constexpr DecoderConfig<SingleBand, 5> X61Config{ + 2, false, {{SideLeft, FrontLeft, FrontRight, SideRight, BackCenter}}, + DevAmbiScaling::N3D, + {{1.0f, 1.0f, 1.0f}}, + {{ + {{2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f}}, + {{1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f}}, + {{1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f}}, + {{2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f}}, + {{2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f}}, + }} +}; +constexpr DecoderConfig<DualBand, 6> X71Config{ + 2, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{1.41421356e+0f, 1.22474487e+0f, 7.07106781e-1f}}, + {{ + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + }} +}; +constexpr DecoderConfig<DualBand, 6> X3D71Config{ + 1, true, {{Aux0, SideLeft, FrontLeft, FrontRight, SideRight, Aux1}}, + DevAmbiScaling::N3D, + /*HF*/{{1.73205081e+0f, 1.00000000e+0f}}, + {{ + {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, + }} +}; +constexpr DecoderConfig<SingleBand, 10> X714Config{ + 1, true, {{FrontLeft, FrontRight, SideLeft, SideRight, BackLeft, BackRight, TopFrontLeft, TopFrontRight, TopBackLeft, TopBackRight }}, + DevAmbiScaling::N3D, + {{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.27149251e-01f, 7.63047539e-02f, -3.64373750e-02f, 1.59700680e-01f}}, + {{1.07005418e-01f, -7.67638760e-02f, -4.92129762e-02f, 1.29012797e-01f}}, + {{1.26400196e-01f, 1.77494694e-01f, -3.71203389e-02f, 0.00000000e+00f}}, + {{1.26396516e-01f, -1.77488059e-01f, -3.71297878e-02f, 0.00000000e+00f}}, + {{1.06996956e-01f, 7.67615256e-02f, -4.92166307e-02f, -1.29001640e-01f}}, + {{1.27145671e-01f, -7.63003471e-02f, -3.64353304e-02f, -1.59697510e-01f}}, + {{8.80919747e-02f, 7.48940670e-02f, 9.08786244e-02f, 6.22527183e-02f}}, + {{1.57880745e-01f, -7.28755272e-02f, 1.82364187e-01f, 8.74240284e-02f}}, + {{1.57892225e-01f, 7.28944768e-02f, 1.82363474e-01f, -8.74301086e-02f}}, + {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, + }} +}; + +void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, + DecoderView decoder={}) +{ + if(!decoder) { - ChannelDec chancoeffs[MAX_OUTPUT_CHANNELS]{}; - ALuint idxmap[MAX_OUTPUT_CHANNELS]{}; - for(size_t i{0u};i < chanmap.size();++i) + switch(device->FmtChans) { - const ALuint idx{GetChannelIdxByName(device->RealOut, chanmap[i].ChanName)}; - if(idx == INVALID_CHANNEL_INDEX) + 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 DevFmtX714: decoder = X714Config; break; + case DevFmtX3D71: decoder = X3D71Config; break; + case DevFmtAmbi3D: + auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); + auto&& n3dscale = GetAmbiScales(device->mAmbiScale); + + /* For DevFmtAmbi3D, the ambisonic order is already set. */ + const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; + std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), + [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig + { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); + AllocChannels(device, count, 0); + device->m2DMixing = false; + + float avg_dist{}; + if(auto distopt = device->configValue<float>("decoder", "speaker-dist")) + avg_dist = *distopt; + else if(auto delayopt = device->configValue<float>("decoder", "nfc-ref-delay")) { - ERR("Failed to find %s channel in device\n", - GetLabelFromChannel(chanmap[i].ChanName)); - continue; + WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n"); + avg_dist = *delayopt * SpeedOfSoundMetersPerSec; } - idxmap[i] = idx; - std::copy_n(chanmap[i].Config, coeffcount, chancoeffs[i]); - } - /* For non-DevFmtAmbi3D, set the ambisonic order given the mixing - * channel count. Built-in speaker decoders are always 2D, so just - * reverse that calculation. - */ - device->mAmbiOrder = (coeffcount-1) / 2; - - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+coeffcount, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - AllocChannels(device, coeffcount, device->channelsFromFmt()); - - TRACE("Enabling %s-order%s ambisonic decoder\n", - (coeffcount > 5) ? "third" : - (coeffcount > 3) ? "second" : "first", - "" - ); - device->AmbiDecoder = al::make_unique<BFormatDec>(coeffcount, - static_cast<ALsizei>(chanmap.size()), chancoeffs, idxmap); + InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); + return; + } } -} -void InitCustomPanning(ALCdevice *device, bool hqdec, const AmbDecConf *conf, - const ALuint (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - static const ALuint chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 }; - static const ALuint chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 }; + const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : + Ambi2DChannelsFromOrder(decoder.mOrder)}; + const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; + al::vector<ChannelDec> chancoeffs, chancoeffslf; + for(size_t i{0u};i < decoder.mChannels.size();++i) + { + const uint idx{device->channelIdxByName(decoder.mChannels[i])}; + if(idx == InvalidChannelIndex) + { + ERR("Failed to find %s channel in device\n", + GetLabelFromChannel(decoder.mChannels[i])); + continue; + } - if(!hqdec && conf->FreqBands != 1) - ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n", - conf->XOverFreq); + auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data() + : AmbiIndex::OrderFrom2DChannel().data(); - const ALuint order{(conf->ChanMask > AMBI_2ORDER_MASK) ? 3u : - (conf->ChanMask > AMBI_1ORDER_MASK) ? 2u : 1u}; - device->mAmbiOrder = order; + chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); + al::span<const float,MaxAmbiChannels> src{decoder.mCoeffs[i]}; + al::span<float,MaxAmbiChannels> dst{chancoeffs[idx]}; + for(size_t ambichan{0};ambichan < ambicount;++ambichan) + dst[ambichan] = src[ambichan] * decoder.mOrderGain[ordermap[ambichan]]; - ALuint count; - if((conf->ChanMask&AMBI_PERIPHONIC_MASK)) - { - count = static_cast<ALuint>(AmbiChannelsFromOrder(order)); - std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + if(!dual_band) + continue; + + chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); + src = decoder.mCoeffsLF[i]; + dst = chancoeffslf[idx]; + for(size_t ambichan{0};ambichan < ambicount;++ambichan) + dst[ambichan] = src[ambichan] * decoder.mOrderGainLF[ordermap[ambichan]]; } - else + + /* For non-DevFmtAmbi3D, set the ambisonic order. */ + device->mAmbiOrder = decoder.mOrder; + device->m2DMixing = !decoder.mIs3D; + + const al::span<const uint8_t> 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<FrontStablizer> stablizer; + if(stablize) { - count = static_cast<ALuint>(Ambi2DChannelsFromOrder(order)); - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + /* Only enable the stablizer if the decoder does not output to the + * front-center channel. + */ + const auto cidx = device->RealOut.ChannelIndex[FrontCenter]; + bool hasfc{false}; + if(cidx < chancoeffs.size()) + { + for(const auto &coeff : chancoeffs[cidx]) + hasfc |= coeff != 0.0f; + } + if(!hasfc && cidx < chancoeffslf.size()) + { + for(const auto &coeff : chancoeffslf[cidx]) + hasfc |= coeff != 0.0f; + } + if(!hasfc) + { + stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency); + TRACE("Front stablizer enabled\n"); + } } - AllocChannels(device, count, device->channelsFromFmt()); TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - (!hqdec || conf->FreqBands == 1) ? "single" : "dual", - (conf->ChanMask > AMBI_2ORDER_MASK) ? "third" : - (conf->ChanMask > AMBI_1ORDER_MASK) ? "second" : "first", - (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? " periphonic" : "" - ); - device->AmbiDecoder = al::make_unique<BFormatDec>(conf, hqdec, count, device->Frequency, - speakermap); - - auto accum_spkr_dist = std::bind(std::plus<float>{}, _1, - std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)); - const ALfloat avg_dist{ - std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), 0.0f, accum_spkr_dist) / - static_cast<ALfloat>(conf->Speakers.size())}; - InitNearFieldCtrl(device, avg_dist, order, - (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? chans_per_order3d : chans_per_order2d); - - InitDistanceComp(device, conf, speakermap); + !dual_band ? "single" : "dual", + (decoder.mOrder > 3) ? "fourth" : + (decoder.mOrder > 2) ? "third" : + (decoder.mOrder > 1) ? "second" : "first", + decoder.mIs3D ? " periphonic" : ""); + device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, + device->mXOverFreq/static_cast<float>(device->Frequency), std::move(stablizer)); } void InitHrtfPanning(ALCdevice *device) { - constexpr float PI{al::MathDefs<float>::Pi()}; - constexpr float PI_2{al::MathDefs<float>::Pi() / 2.0f}; - constexpr float PI_4{al::MathDefs<float>::Pi() / 4.0f}; - constexpr float PI3_4{al::MathDefs<float>::Pi() * 3.0f / 4.0f}; - const float CornerElev{static_cast<float>(std::atan2(1.0, std::sqrt(2.0)))}; + constexpr float Deg180{al::numbers::pi_v<float>}; + constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; + constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; + constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; + constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; + constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; + constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; + constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; + constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; + constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; static const AngularPoint AmbiPoints1O[]{ - { ElevRadius{ CornerElev}, AzimRadius{ -PI_4} }, - { ElevRadius{ CornerElev}, AzimRadius{-PI3_4} }, - { ElevRadius{ CornerElev}, AzimRadius{ PI_4} }, - { ElevRadius{ CornerElev}, AzimRadius{ PI3_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ -PI_4} }, - { ElevRadius{-CornerElev}, AzimRadius{-PI3_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ PI_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ PI3_4} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, }, AmbiPoints2O[]{ - { ElevRadius{ 0.0f}, AzimRadius{ 0.0f} }, - { ElevRadius{ 0.0f}, AzimRadius{ PI} }, - { ElevRadius{ 0.0f}, AzimRadius{ -PI_2} }, - { ElevRadius{ 0.0f}, AzimRadius{ PI_2} }, - { ElevRadius{ PI_2}, AzimRadius{ 0.0f} }, - { ElevRadius{ -PI_2}, AzimRadius{ 0.0f} }, - { ElevRadius{ PI_4}, AzimRadius{ -PI_2} }, - { ElevRadius{ PI_4}, AzimRadius{ PI_2} }, - { ElevRadius{ -PI_4}, AzimRadius{ -PI_2} }, - { ElevRadius{ -PI_4}, AzimRadius{ PI_2} }, - { ElevRadius{ PI_4}, AzimRadius{ 0.0f} }, - { ElevRadius{ PI_4}, AzimRadius{ PI} }, - { ElevRadius{ -PI_4}, AzimRadius{ 0.0f} }, - { ElevRadius{ -PI_4}, AzimRadius{ PI} }, - { ElevRadius{ 0.0f}, AzimRadius{ -PI_4} }, - { ElevRadius{ 0.0f}, AzimRadius{-PI3_4} }, - { ElevRadius{ 0.0f}, AzimRadius{ PI_4} }, - { ElevRadius{ 0.0f}, AzimRadius{ PI3_4} }, - { ElevRadius{ CornerElev}, AzimRadius{ -PI_4} }, - { ElevRadius{ CornerElev}, AzimRadius{-PI3_4} }, - { ElevRadius{ CornerElev}, AzimRadius{ PI_4} }, - { ElevRadius{ CornerElev}, AzimRadius{ PI3_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ -PI_4} }, - { ElevRadius{-CornerElev}, AzimRadius{-PI3_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ PI_4} }, - { ElevRadius{-CornerElev}, AzimRadius{ PI3_4} }, + { EvRadians{-Deg_32}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_58} }, + { EvRadians{ Deg_58}, AzRadians{ Deg_90} }, + { EvRadians{ Deg_32}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg122} }, + { EvRadians{-Deg_58}, AzRadians{-Deg_90} }, + { EvRadians{-Deg_32}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg122} }, + { EvRadians{ Deg_58}, AzRadians{-Deg_90} }, + { EvRadians{ Deg_32}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_58} }, + { EvRadians{-Deg_58}, AzRadians{ Deg_90} }, + }, AmbiPoints3O[]{ + { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_69} }, + { EvRadians{ 0.0f}, AzRadians{-Deg111} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_69} }, + { EvRadians{ 0.0f}, AzRadians{ Deg111} }, + { EvRadians{ Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{ Deg_21}, AzRadians{ Deg180} }, + { EvRadians{-Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{-Deg_21}, AzRadians{ Deg180} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, }; - static const float AmbiMatrix1O[][MAX_AMBI_CHANNELS]{ + static const float AmbiMatrix1O[][MaxAmbiChannels]{ { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f }, { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, @@ -573,64 +771,91 @@ 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 }, { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, - }, AmbiMatrix2O[][MAX_AMBI_CHANNELS]{ - { 3.846153846e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 8.606629658e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 8.606629658e-02f }, - { 3.846153846e-02f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, -8.606629658e-02f }, - { 3.846153846e-02f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, -8.606629658e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 9.938079900e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, 0.000000000e+00f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 9.938079900e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, 4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f }, - { 3.846153846e-02f, -4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f }, - { 3.846153846e-02f, 4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f }, - { 3.846153846e-02f, -4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, 4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, 6.834676493e-02f, 4.303314829e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, 4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, -6.834676493e-02f, 4.303314829e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, -4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, -6.834676493e-02f, 4.303314829e-02f }, - { 3.846153846e-02f, 0.000000000e+00f, -4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, 6.834676493e-02f, 4.303314829e-02f }, - { 3.846153846e-02f, 4.710557198e-02f, 0.000000000e+00f, 4.710557198e-02f, 6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, 4.710557198e-02f, 0.000000000e+00f, -4.710557198e-02f, -6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, -4.710557198e-02f, 0.000000000e+00f, 4.710557198e-02f, -6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, -4.710557198e-02f, 0.000000000e+00f, -4.710557198e-02f, 6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f }, - { 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, 4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, -4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, -4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, 4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, 4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, -4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, -4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f }, - { 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, 4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f }, + }, AmbiMatrix2O[][MaxAmbiChannels]{ + { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + }, AmbiMatrix3O[][MaxAmbiChannels]{ + { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 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[MAX_AMBI_ORDER+1]{ - 2.000000000e+00f, 1.154700538e+00f - }, AmbiOrderHFGain2O[MAX_AMBI_ORDER+1]{ - 2.687419249e+00f, 2.081665999e+00f, 1.074967700e+00f + static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ + /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f + }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ + /*ENRGY*/ 1.825741858e+00f, 1.414213562e+00f, 7.302967433e-01f + /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ + /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ + }, 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 const ALuint ChansPerOrder[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; 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 = HrtfRender; - ALuint ambi_order{1}; - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf-mode")) + device->mRenderMode = RenderMode::Hrtf; + uint ambi_order{1}; + if(auto modeopt = device->configValue<std::string>(nullptr, "hrtf-mode")) { struct HrtfModeEntry { char name[8]; RenderMode mode; - ALuint order; + uint order; }; static const HrtfModeEntry hrtf_modes[]{ - { "full", HrtfRender, 1 }, - { "ambi1", NormalRender, 1 }, - { "ambi2", NormalRender, 2 }, + { "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"; @@ -652,40 +877,43 @@ void InitHrtfPanning(ALCdevice *device) ((ambi_order%10) == 1) ? "st" : ((ambi_order%10) == 2) ? "nd" : ((ambi_order%10) == 3) ? "rd" : "th", - (device->mRenderMode == HrtfRender) ? "+ Full " : "", - device->HrtfName.c_str()); - - al::span<const AngularPoint> AmbiPoints{}; - const float (*AmbiMatrix)[MAX_AMBI_CHANNELS]{}; - const float *AmbiOrderHFGain{}; - if(ambi_order >= 2) + (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", + device->mHrtfName.c_str()); + + bool perHrirMin{false}; + al::span<const AngularPoint> AmbiPoints{AmbiPoints1O}; + const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; + al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain{AmbiOrderHFGain1O}; + if(ambi_order >= 3) + { + perHrirMin = true; + AmbiPoints = AmbiPoints3O; + AmbiMatrix = AmbiMatrix3O; + AmbiOrderHFGain = AmbiOrderHFGain3O; + } + else if(ambi_order == 2) { AmbiPoints = AmbiPoints2O; AmbiMatrix = AmbiMatrix2O; AmbiOrderHFGain = AmbiOrderHFGain2O; } - else /*if(ambi_order == 1)*/ - { - AmbiPoints = AmbiPoints1O; - AmbiMatrix = AmbiMatrix1O; - AmbiOrderHFGain = AmbiOrderHFGain1O; - } device->mAmbiOrder = ambi_order; + device->m2DMixing = false; const size_t count{AmbiChannelsFromOrder(ambi_order)}; - device->mHrtfState = DirectHrtfState::Create(count); - - std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count, + std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count, std::begin(device->Dry.AmbiMap), [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } ); - AllocChannels(device, static_cast<ALuint>(count), device->channelsFromFmt()); + AllocChannels(device, count, device->channelsFromFmt()); - BuildBFormatHrtf(device->mHrtf, device->mHrtfState.get(), AmbiPoints, AmbiMatrix, + HrtfStore *Hrtf{device->mHrtf.get()}; + auto hrtfstate = DirectHrtfState::Create(count); + hrtfstate->build(Hrtf, device->mIrSize, perHrirMin, AmbiPoints, AmbiMatrix, device->mXOverFreq, AmbiOrderHFGain); + device->mHrtfState = std::move(hrtfstate); - HrtfEntry *Hrtf{device->mHrtf}; - InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, ChansPerOrder); + InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); } void InitUhjPanning(ALCdevice *device) @@ -694,179 +922,204 @@ void InitUhjPanning(ALCdevice *device) constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; + device->m2DMixing = true; - auto acnmap_end = AmbiIndex::FromFuMa.begin() + count; - std::transform(AmbiIndex::FromFuMa.begin(), acnmap_end, std::begin(device->Dry.AmbiMap), + auto acnmap_begin = AmbiIndex::FromFuMa2D().begin(); + std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromFuMa[acn], acn}; } - ); - AllocChannels(device, ALuint{count}, device->channelsFromFmt()); + { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); + AllocChannels(device, count, device->channelsFromFmt()); } } // namespace -void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq) +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ - HrtfEntry *old_hrtf{device->mHrtf}; + HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; device->mHrtfState = nullptr; device->mHrtf = nullptr; - device->HrtfName.clear(); - device->mRenderMode = NormalRender; + device->mIrSize = 0; + device->mHrtfName.clear(); + device->mXOverFreq = 400.0f; + device->m2DMixing = false; + device->mRenderMode = RenderMode::Normal; if(device->FmtChans != DevFmtStereo) { - if(old_hrtf) - old_hrtf->DecRef(); old_hrtf = nullptr; - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + if(stereomode && *stereomode == StereoEncoding::Hrtf) + device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; const char *layout{nullptr}; switch(device->FmtChans) { - case DevFmtQuad: layout = "quad"; break; - case DevFmtX51: /* fall-through */ - case DevFmtX51Rear: layout = "surround51"; break; - case DevFmtX61: layout = "surround61"; break; - case DevFmtX71: layout = "surround71"; break; - /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtAmbi3D: - break; + case DevFmtQuad: layout = "quad"; break; + case DevFmtX51: layout = "surround51"; break; + case DevFmtX61: layout = "surround61"; break; + case DevFmtX71: layout = "surround71"; break; + case DevFmtX714: layout = "surround714"; break; + case DevFmtX3D71: layout = "surround3d71"; break; + /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ + case DevFmtMono: + case DevFmtStereo: + case DevFmtAmbi3D: + break; } - const char *devname{device->DeviceName.c_str()}; - ALuint speakermap[MAX_OUTPUT_CHANNELS]; - AmbDecConf *pconf{nullptr}; - AmbDecConf conf{}; - if(layout) + std::unique_ptr<DecoderConfig<DualBand,MAX_OUTPUT_CHANNELS>> decoder_store; + DecoderView decoder{}; + float speakerdists[MAX_OUTPUT_CHANNELS]{}; + auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { - if(auto decopt = ConfigValueStr(devname, "decoder", layout)) + AmbDecConf conf{}; + if(auto err = conf.load(config)) { - if(!conf.load(decopt->c_str())) - ERR("Failed to load layout file %s\n", decopt->c_str()); - else if(conf.Speakers.size() > MAX_OUTPUT_CHANNELS) - ERR("Unsupported speaker count %zu (max %d)\n", conf.Speakers.size(), - MAX_OUTPUT_CHANNELS); - else if(conf.ChanMask > AMBI_3ORDER_MASK) - ERR("Unsupported channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, - AMBI_3ORDER_MASK); - else if(MakeSpeakerMap(device, &conf, speakermap)) - pconf = &conf; + ERR("Failed to load layout file %s\n", config); + ERR(" %s\n", err->c_str()); } - } + else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) + ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, + MAX_OUTPUT_CHANNELS); + else if(conf.ChanMask > Ambi3OrderMask) + ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, + Ambi3OrderMask); + else + { + device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f); - if(!pconf) - InitPanning(device); - else + decoder_store = std::make_unique<DecoderConfig<DualBand,MAX_OUTPUT_CHANNELS>>(); + decoder = MakeDecoderView(device, &conf, *decoder_store); + for(size_t i{0};i < decoder.mChannels.size();++i) + speakerdists[i] = conf.Speakers[i].Distance; + } + }; + if(layout) { - int hqdec{GetConfigValueBool(devname, "decoder", "hq-mode", 1)}; - InitCustomPanning(device, !!hqdec, pconf, speakermap); + if(auto decopt = device->configValue<std::string>("decoder", layout)) + load_config(decopt->c_str()); } - if(device->AmbiDecoder) - device->PostProcess = &ALCdevice::ProcessAmbiDec; - return; - } - bool headphones{device->IsHeadphones != AL_FALSE}; - if(device->Type != Loopback) - { - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-mode")) + /* Enable the stablizer only for formats that have front-left, front- + * right, and front-center outputs. + */ + const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex + && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex + && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex + && device->getConfigValueBool(nullptr, "front-stablizer", false) != 0}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true) != 0}; + InitPanning(device, hqdec, stablize, decoder); + if(decoder) { - 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); - } - } + 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(hrtf_userreq == Hrtf_Default) - { - bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) || - (hrtf_appreq == Hrtf_Enable); - if(!usehrtf) goto no_hrtf; + const float avg_dist{(accum_dist > 0.0f && spkr_count > 0) ? accum_dist/spkr_count : + device->configValue<float>("decoder", "speaker-dist").value_or(1.0f)}; + InitNearFieldCtrl(device, avg_dist, decoder.mOrder, decoder.mIs3D); - 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(spkr_count > 0) + InitDistanceComp(device, decoder.mChannels, speakerdists); + } + if(auto *ambidec{device->AmbiDecoder.get()}) { - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_DENIED_SOFT; - goto no_hrtf; + device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized + : &ALCdevice::ProcessAmbiDec; } - device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT; + return; } - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - if(hrtf_id >= 0 && static_cast<ALuint>(hrtf_id) < device->HrtfList.size()) + /* 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))) { - const EnumeratedHrtf &entry = device->HrtfList[static_cast<ALuint>(hrtf_id)]; - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(hrtf && hrtf->sampleRate == device->Frequency) + if(device->mHrtfList.empty()) + device->enumerateHrtfs(); + + if(hrtf_id >= 0 && static_cast<uint>(hrtf_id) < device->mHrtfList.size()) { - device->mHrtf = hrtf; - device->HrtfName = entry.name; + const std::string &hrtfname = device->mHrtfList[static_cast<uint>(hrtf_id)]; + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + { + device->mHrtf = std::move(hrtf); + device->mHrtfName = hrtfname; + } } - else if(hrtf) - hrtf->DecRef(); - } - if(!device->mHrtf) - { - auto find_hrtf = [device](const EnumeratedHrtf &entry) -> bool + if(!device->mHrtf) { - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(!hrtf) return false; - if(hrtf->sampleRate != device->Frequency) + for(const auto &hrtfname : device->mHrtfList) { - hrtf->DecRef(); - return false; + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + { + device->mHrtf = std::move(hrtf); + device->mHrtfName = hrtfname; + break; + } } - device->mHrtf = hrtf; - device->HrtfName = entry.name; - return true; - }; - std::find_if(device->HrtfList.cbegin(), device->HrtfList.cend(), find_hrtf); + } + + if(device->mHrtf) + { + old_hrtf = nullptr; + + HrtfStore *hrtf{device->mHrtf.get()}; + device->mIrSize = hrtf->mIrSize; + if(auto hrtfsizeopt = device->configValue<uint>(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; - if(device->mHrtf) + if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) { - if(old_hrtf) - old_hrtf->DecRef(); - old_hrtf = nullptr; + switch(UhjEncodeQuality) + { + case UhjQualityType::IIR: + device->mUhjEncoder = std::make_unique<UhjEncoderIIR>(); + break; + case UhjQualityType::FIR256: + device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength256>>(); + break; + case UhjQualityType::FIR512: + device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength512>>(); + break; + } + assert(device->mUhjEncoder != nullptr); - InitHrtfPanning(device); - device->PostProcess = &ALCdevice::ProcessHrtf; + TRACE("UHJ enabled\n"); + InitUhjPanning(device); + device->PostProcess = &ALCdevice::ProcessUhj; return; } - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; - -no_hrtf: - if(old_hrtf) - old_hrtf->DecRef(); - old_hrtf = nullptr; - device->mRenderMode = StereoPair; - - if(device->Type != Loopback) + 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<int>(nullptr, "cf_level")) { if(*cflevopt > 0 && *cflevopt <= 6) { - device->Bs2b = al::make_unique<bs2b>(); + device->Bs2b = std::make_unique<bs2b>(); bs2b_set_params(device->Bs2b.get(), *cflevopt, static_cast<int>(device->Frequency)); TRACE("BS2B enabled\n"); @@ -877,145 +1130,23 @@ 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 = NormalRender; - else if(al::strcasecmp(mode, "panpot") != 0) - ERR("Unexpected stereo-encoding: %s\n", mode); - } - if(device->mRenderMode == NormalRender) - { - device->Uhj_Encoder = al::make_unique<Uhj2Encoder>(); - TRACE("UHJ enabled\n"); - InitUhjPanning(device); - device->PostProcess = &ALCdevice::ProcessUhj; - return; - } - TRACE("Stereo rendering\n"); InitPanning(device); device->PostProcess = &ALCdevice::ProcessAmbiDec; } -void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device) +void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) { + DeviceBase *device{context->mDevice}; const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - slot->MixBuffer.resize(count); - slot->MixBuffer.shrink_to_fit(); - auto acnmap_end = AmbiIndex::From3D.begin() + count; - auto iter = std::transform(AmbiIndex::From3D.begin(), acnmap_end, slot->Wet.AmbiMap.begin(), + slot->mWetBuffer.resize(count); + + auto acnmap_begin = AmbiIndex::FromACN().begin(); + auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f, acn}; } - ); + { return BFChannelConfig{1.0f, acn}; }); std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); - slot->Wet.Buffer = {slot->MixBuffer.data(), slot->MixBuffer.size()}; -} - - -void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread, - const al::span<float,MAX_AMBI_CHANNELS> 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 */ - coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */ - coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */ - coeffs[6] = 1.118033989f * (z*z*3.0f - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ - coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */ - coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ - /* Third-order */ - coeffs[9] = 2.091650066f * y * (x*x*3.0f - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ - coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */ - coeffs[11] = 1.620185175f * y * (z*z*5.0f - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ - coeffs[12] = 1.322875656f * z * (z*z*5.0f - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ - coeffs[13] = 1.620185175f * x * (z*z*5.0f - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ - coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ - coeffs[15] = 2.091650066f * x * (x*x - y*y*3.0f); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ - /* Fourth-order */ - /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ - /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ - /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ - /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ - /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ - /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ - /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ - /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ - /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ - - if(spread > 0.0f) - { - /* Implement the spread by using a spherical source that subtends the - * angle spread. See: - * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 - * - * When adjusted for N3D normalization instead of SN3D, these - * calculations are: - * - * ZH0 = -sqrt(pi) * (-1+ca); - * ZH1 = 0.5*sqrt(pi) * sa*sa; - * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); - * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); - * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); - * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); - * - * The gain of the source is compensated for size, so that the - * loudness doesn't depend on the spread. Thus: - * - * ZH0 = 1.0f; - * ZH1 = 0.5f * (ca+1.0f); - * ZH2 = 0.5f * (ca+1.0f)*ca; - * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); - * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; - * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); - */ - 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<float>::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; - } -} - -void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span<float,MAX_OUTPUT_CHANNELS> 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); + slot->Wet.Buffer = slot->mWetBuffer; } diff --git a/alc/ringbuffer.cpp b/alc/ringbuffer.cpp deleted file mode 100644 index 1f72f4b1..00000000 --- a/alc/ringbuffer.cpp +++ /dev/null @@ -1,251 +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 "ringbuffer.h" - -#include <algorithm> -#include <climits> -#include <cstdint> -#include <stdexcept> - -#include "almalloc.h" - - -RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes) -{ - size_t power_of_two{0u}; - if(sz > 0) - { - power_of_two = sz; - power_of_two |= power_of_two>>1; - power_of_two |= power_of_two>>2; - power_of_two |= power_of_two>>4; - power_of_two |= power_of_two>>8; - power_of_two |= power_of_two>>16; -#if SIZE_MAX > UINT_MAX - power_of_two |= power_of_two>>32; -#endif - } - ++power_of_two; - if(power_of_two <= sz || power_of_two > std::numeric_limits<size_t>::max()/elem_sz) - throw std::overflow_error{"Ring buffer size overflow"}; - - const size_t bufbytes{power_of_two * elem_sz}; - RingBufferPtr rb{new (FamCount{bufbytes}) RingBuffer{bufbytes}}; - rb->mWriteSize = limit_writes ? sz : (power_of_two-1); - rb->mSizeMask = power_of_two - 1; - rb->mElemSize = elem_sz; - - return rb; -} - -void RingBuffer::reset() noexcept -{ - mWritePtr.store(0, std::memory_order_relaxed); - mReadPtr.store(0, std::memory_order_relaxed); - std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{}); -} - - -size_t RingBuffer::readSpace() const noexcept -{ - size_t w = mWritePtr.load(std::memory_order_acquire); - size_t r = mReadPtr.load(std::memory_order_acquire); - return (w-r) & mSizeMask; -} - -size_t RingBuffer::writeSpace() const noexcept -{ - size_t w = mWritePtr.load(std::memory_order_acquire); - size_t r = mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask; - return (r-w-1) & mSizeMask; -} - - -size_t RingBuffer::read(void *dest, size_t cnt) noexcept -{ - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; - - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; - - size_t n1, n2; - const size_t cnt2{read_ptr + to_read}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - read_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_read; - n2 = 0; - } - - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast<al::byte*>(dest)); - read_ptr += n1; - if(n2 > 0) - { - std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); - read_ptr += n2; - } - mReadPtr.store(read_ptr, std::memory_order_release); - return to_read; -} - -size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept -{ - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; - - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; - - size_t n1, n2; - const size_t cnt2{read_ptr + to_read}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - read_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_read; - n2 = 0; - } - - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast<al::byte*>(dest)); - if(n2 > 0) - std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); - return to_read; -} - -size_t RingBuffer::write(const void *src, size_t cnt) noexcept -{ - const size_t free_cnt{writeSpace()}; - if(free_cnt == 0) return 0; - - const size_t to_write{std::min(cnt, free_cnt)}; - size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask}; - - size_t n1, n2; - const size_t cnt2{write_ptr + to_write}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - write_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_write; - n2 = 0; - } - - auto srcbytes = static_cast<const al::byte*>(src); - std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize); - write_ptr += n1; - if(n2 > 0) - { - std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin()); - write_ptr += n2; - } - mWritePtr.store(write_ptr, std::memory_order_release); - return to_write; -} - - -void RingBuffer::readAdvance(size_t cnt) noexcept -{ - mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); -} - -void RingBuffer::writeAdvance(size_t cnt) noexcept -{ - mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); -} - - -ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept -{ - ll_ringbuffer_data_pair ret; - - size_t w{mWritePtr.load(std::memory_order_acquire)}; - size_t r{mReadPtr.load(std::memory_order_acquire)}; - w &= mSizeMask; - r &= mSizeMask; - const size_t free_cnt{(w-r) & mSizeMask}; - - const size_t cnt2{r + free_cnt}; - if(cnt2 > mSizeMask+1) - { - /* Two part vector: the rest of the buffer after the current read ptr, - * plus some from the start of the buffer. */ - ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize); - ret.first.len = mSizeMask+1 - r; - ret.second.buf = const_cast<al::byte*>(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; - } - else - { - /* Single part vector: just the rest of the buffer */ - ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; -} - -ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept -{ - ll_ringbuffer_data_pair ret; - - size_t w{mWritePtr.load(std::memory_order_acquire)}; - size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; - w &= mSizeMask; - r &= mSizeMask; - const size_t free_cnt{(r-w-1) & mSizeMask}; - - const size_t cnt2{w + free_cnt}; - if(cnt2 > mSizeMask+1) - { - /* Two part vector: the rest of the buffer after the current write ptr, - * plus some from the start of the buffer. */ - ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize); - ret.first.len = mSizeMask+1 - w; - ret.second.buf = const_cast<al::byte*>(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; - } - else - { - ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; -} diff --git a/alc/ringbuffer.h b/alc/ringbuffer.h deleted file mode 100644 index 3151fdcb..00000000 --- a/alc/ringbuffer.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef RINGBUFFER_H -#define RINGBUFFER_H - -#include <stddef.h> - -#include <atomic> -#include <memory> -#include <utility> - -#include "albyte.h" -#include "almalloc.h" - - -/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended - * to include an element size. Consequently, parameters and return values for a - * size or count is in 'elements', not bytes. Additionally, it only supports - * single-consumer/single-provider operation. - */ - -struct ll_ringbuffer_data { - al::byte *buf; - size_t len; -}; -using ll_ringbuffer_data_pair = std::pair<ll_ringbuffer_data,ll_ringbuffer_data>; - - -struct RingBuffer { - std::atomic<size_t> mWritePtr{0u}; - std::atomic<size_t> mReadPtr{0u}; - size_t mWriteSize{0u}; - size_t mSizeMask{0u}; - size_t mElemSize{0u}; - - al::FlexArray<al::byte, 16> mBuffer; - - RingBuffer(const size_t count) : mBuffer{count} { } - - /** Reset the read and write pointers to zero. This is not thread safe. */ - void reset() noexcept; - - /** - * The non-copying data reader. Returns two ringbuffer data pointers that - * 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; - /** - * 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; - - /** - * Return the number of elements available for reading. This is the number - * of elements in front of the read pointer and behind the write pointer. - */ - size_t readSpace() const noexcept; - /** - * The copying data reader. Copy at most `cnt' elements into `dest'. - * Returns the actual number of elements copied. - */ - size_t read(void *dest, size_t cnt) noexcept; - /** - * The copying data reader w/o read pointer advance. Copy at most `cnt' - * elements into `dest'. Returns the actual number of elements copied. - */ - size_t peek(void *dest, size_t cnt) const noexcept; - /** Advance the read pointer `cnt' places. */ - void readAdvance(size_t cnt) noexcept; - - /** - * Return the number of elements available for writing. This is the number - * of elements in front of the write pointer and behind the read pointer. - */ - size_t writeSpace() const noexcept; - /** - * The copying data writer. Copy at most `cnt' elements from `src'. Returns - * the actual number of elements copied. - */ - size_t write(const void *src, size_t cnt) noexcept; - /** Advance the write pointer `cnt' places. */ - void writeAdvance(size_t cnt) noexcept; - - DEF_FAM_NEWDEL(RingBuffer, mBuffer) -}; -using RingBufferPtr = std::unique_ptr<RingBuffer>; - - -/** - * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' bytes. - * The number of elements is rounded up to the next power of two (even if it is - * already a power of two, to ensure the requested amount can be written). - */ -RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes); - -#endif /* RINGBUFFER_H */ diff --git a/alc/uhjfilter.cpp b/alc/uhjfilter.cpp deleted file mode 100644 index 7d01a91f..00000000 --- a/alc/uhjfilter.cpp +++ /dev/null @@ -1,138 +0,0 @@ - -#include "config.h" - -#include "uhjfilter.h" - -#include <algorithm> -#include <iterator> - -#include "AL/al.h" - -#include "alnumeric.h" -#include "opthelpers.h" - - -namespace { - -/* This is the maximum number of samples processed for each inner loop - * iteration. */ -#define MAX_UPDATE_SAMPLES 128 - - -constexpr ALfloat Filter1CoeffSqr[4] = { - 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f -}; -constexpr ALfloat Filter2CoeffSqr[4] = { - 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f -}; - -void allpass_process(AllPassState *state, ALfloat *dst, const ALfloat *src, const ALfloat aa, - const size_t todo) -{ - ALfloat z1{state->z[0]}; - ALfloat z2{state->z[1]}; - auto proc_sample = [aa,&z1,&z2](const ALfloat input) noexcept -> ALfloat - { - const ALfloat output{input*aa + z1}; - z1 = z2; z2 = output*aa - input; - return output; - }; - std::transform(src, src+todo, dst, proc_sample); - state->z[0] = z1; - state->z[1] = z2; -} - -} // namespace - - -/* NOTE: There seems to be a bit of an inconsistency in how this encoding is - * supposed to work. Some references, such as - * - * http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html - * - * specify a pre-scaling of sqrt(2) on the W channel input, while other - * references, such as - * - * https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D - * and - * https://wiki.xiph.org/Ambisonics#UHJ_format - * - * do not. The sqrt(2) scaling is in line with B-Format decoder coefficients - * which include such a scaling for the W channel input, however the original - * source for this equation is a 1985 paper by Michael Gerzon, which does not - * apparently include the scaling. Applying the extra scaling creates a louder - * result with a narrower stereo image compared to not scaling, and I don't - * know which is the intended result. - */ - -void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - FloatBufferLine *InSamples, const size_t SamplesToDo) -{ - alignas(16) ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES]; - alignas(16) ALfloat temp[MAX_UPDATE_SAMPLES]; - - ASSUME(SamplesToDo > 0); - - auto winput = InSamples[0].cbegin(); - auto xinput = InSamples[1].cbegin(); - auto yinput = InSamples[2].cbegin(); - for(size_t base{0};base < SamplesToDo;) - { - const size_t todo{minz(SamplesToDo - base, MAX_UPDATE_SAMPLES)}; - ASSUME(todo > 0); - - /* D = 0.6554516*Y */ - std::transform(yinput, yinput+todo, std::begin(temp), - [](const float y) noexcept -> float { return 0.6554516f*y; }); - allpass_process(&mFilter1_Y[0], temp, temp, Filter1CoeffSqr[0], todo); - allpass_process(&mFilter1_Y[1], temp, temp, Filter1CoeffSqr[1], todo); - allpass_process(&mFilter1_Y[2], temp, temp, Filter1CoeffSqr[2], todo); - allpass_process(&mFilter1_Y[3], temp, temp, Filter1CoeffSqr[3], todo); - /* NOTE: Filter1 requires a 1 sample delay for the final output, so - * take the last processed sample from the previous run as the first - * output sample. - */ - D[0] = mLastY; - for(size_t i{1};i < todo;i++) - D[i] = temp[i-1]; - mLastY = temp[todo-1]; - - /* D += j(-0.3420201*W + 0.5098604*X) */ - std::transform(winput, winput+todo, xinput, std::begin(temp), - [](const float w, const float x) noexcept -> float - { return -0.3420201f*w + 0.5098604f*x; }); - allpass_process(&mFilter2_WX[0], temp, temp, Filter2CoeffSqr[0], todo); - allpass_process(&mFilter2_WX[1], temp, temp, Filter2CoeffSqr[1], todo); - allpass_process(&mFilter2_WX[2], temp, temp, Filter2CoeffSqr[2], todo); - allpass_process(&mFilter2_WX[3], temp, temp, Filter2CoeffSqr[3], todo); - for(size_t i{0};i < todo;i++) - D[i] += temp[i]; - - /* S = 0.9396926*W + 0.1855740*X */ - std::transform(winput, winput+todo, xinput, std::begin(temp), - [](const float w, const float x) noexcept -> float - { return 0.9396926f*w + 0.1855740f*x; }); - allpass_process(&mFilter1_WX[0], temp, temp, Filter1CoeffSqr[0], todo); - allpass_process(&mFilter1_WX[1], temp, temp, Filter1CoeffSqr[1], todo); - allpass_process(&mFilter1_WX[2], temp, temp, Filter1CoeffSqr[2], todo); - allpass_process(&mFilter1_WX[3], temp, temp, Filter1CoeffSqr[3], todo); - S[0] = mLastWX; - for(size_t i{1};i < todo;i++) - S[i] = temp[i-1]; - mLastWX = temp[todo-1]; - - /* Left = (S + D)/2.0 */ - ALfloat *RESTRICT left = al::assume_aligned<16>(LeftOut.data()+base); - for(size_t i{0};i < todo;i++) - left[i] += (S[i] + D[i]) * 0.5f; - /* Right = (S - D)/2.0 */ - ALfloat *RESTRICT right = al::assume_aligned<16>(RightOut.data()+base); - for(size_t i{0};i < todo;i++) - right[i] += (S[i] - D[i]) * 0.5f; - - winput += todo; - xinput += todo; - yinput += todo; - base += todo; - } -} diff --git a/alc/uhjfilter.h b/alc/uhjfilter.h deleted file mode 100644 index 88d30351..00000000 --- a/alc/uhjfilter.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef UHJFILTER_H -#define UHJFILTER_H - -#include "AL/al.h" - -#include "alcmain.h" -#include "almalloc.h" - - -struct AllPassState { - ALfloat z[2]{0.0f, 0.0f}; -}; - -/* Encoding 2-channel UHJ from B-Format is done as: - * - * S = 0.9396926*W + 0.1855740*X - * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y - * - * Left = (S + D)/2.0 - * Right = (S - D)/2.0 - * - * where j is a wide-band +90 degree phase shift. - * - * The phase shift is done using a Hilbert transform, described here: - * https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/ - * It works using 2 sets of 4 chained filters. The first filter chain produces - * a phase shift of varying magnitude over a wide range of frequencies, while - * the second filter chain produces a phase shift 90 degrees ahead of the - * first over the same range. - * - * Combining these two stages requires the use of three filter chains. S- - * channel output uses a Filter1 chain on the W and X channel mix, while the D- - * channel output uses a Filter1 chain on the Y channel plus a Filter2 chain on - * the W and X channel mix. This results in the W and X input mix on the D- - * channel output having the required +90 degree phase shift relative to the - * other inputs. - */ - -struct Uhj2Encoder { - AllPassState mFilter1_Y[4]; - AllPassState mFilter2_WX[4]; - AllPassState mFilter1_WX[4]; - ALfloat mLastY{0.0f}, mLastWX{0.0f}; - - /* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input - * signal. The input must use FuMa channel ordering and scaling. - */ - void encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, FloatBufferLine *InSamples, - const size_t SamplesToDo); - - DEF_NEWDEL(Uhj2Encoder) -}; - -#endif /* UHJFILTER_H */ diff --git a/alc/uiddefs.cpp b/alc/uiddefs.cpp deleted file mode 100644 index 244c01a5..00000000 --- a/alc/uiddefs.cpp +++ /dev/null @@ -1,37 +0,0 @@ - -#include "config.h" - - -#ifndef AL_NO_UID_DEFS - -#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) -#define INITGUID -#include <windows.h> -#ifdef HAVE_GUIDDEF_H -#include <guiddef.h> -#else -#include <initguid.h> -#endif - -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); - -DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16); - -DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); -DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6); -DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2); -DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2); -DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17); - -#ifdef HAVE_WASAPI -#include <wtypes.h> -#include <devpropdef.h> -#include <propkeydef.h> -DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); -#endif -#endif - -#endif /* AL_NO_UID_DEFS */ diff --git a/alc/voice.cpp b/alc/voice.cpp deleted file mode 100644 index 1c38f36f..00000000 --- a/alc/voice.cpp +++ /dev/null @@ -1,837 +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 <algorithm> -#include <array> -#include <atomic> -#include <cassert> -#include <climits> -#include <cstddef> -#include <cstdint> -#include <iterator> -#include <memory> -#include <new> -#include <utility> - -#include "AL/al.h" -#include "AL/alc.h" - -#include "al/buffer.h" -#include "al/event.h" -#include "al/source.h" -#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 "cpu_caps.h" -#include "devformat.h" -#include "filters/biquad.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "hrtf.h" -#include "inprogext.h" -#include "logging.h" -#include "mixer/defs.h" -#include "opthelpers.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" - - -static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE, - "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!"); - - -Resampler ResamplerDefault{Resampler::Linear}; - -namespace { - -using HrtfMixerFunc = void(*)(const ALfloat *InSamples, float2 *AccumSamples, const ALuint IrSize, - MixHrtfFilter *hrtfparams, const size_t BufferSize); -using HrtfMixerBlendFunc = void(*)(const ALfloat *InSamples, float2 *AccumSamples, - const ALuint IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams, - const size_t BufferSize); - -HrtfMixerFunc MixHrtfSamples = MixHrtf_<CTag>; -HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_<CTag>; - -inline HrtfMixerFunc SelectHrtfMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixHrtf_<NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixHrtf_<SSETag>; -#endif - return MixHrtf_<CTag>; -} - -inline HrtfMixerBlendFunc SelectHrtfBlendMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixHrtfBlend_<NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixHrtfBlend_<SSETag>; -#endif - return MixHrtfBlend_<CTag>; -} - -} // 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 }, - { "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; - } - - MixHrtfBlendSamples = SelectHrtfBlendMixer(); - MixHrtfSamples = SelectHrtfMixer(); -} - - -namespace { - -/* A quick'n'dirty lookup table to decode a muLaw-encoded byte sample into a - * signed 16-bit sample */ -constexpr ALshort muLawDecompressionTable[256] = { - -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, - -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, - -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, - -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, - -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, - -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, - -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, - -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, - -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, - -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, - -876, -844, -812, -780, -748, -716, -684, -652, - -620, -588, -556, -524, -492, -460, -428, -396, - -372, -356, -340, -324, -308, -292, -276, -260, - -244, -228, -212, -196, -180, -164, -148, -132, - -120, -112, -104, -96, -88, -80, -72, -64, - -56, -48, -40, -32, -24, -16, -8, 0, - 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, - 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, - 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, - 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, - 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, - 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, - 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, - 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, - 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, - 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, - 876, 844, 812, 780, 748, 716, 684, 652, - 620, 588, 556, 524, 492, 460, 428, 396, - 372, 356, 340, 324, 308, 292, 276, 260, - 244, 228, 212, 196, 180, 164, 148, 132, - 120, 112, 104, 96, 88, 80, 72, 64, - 56, 48, 40, 32, 24, 16, 8, 0 -}; - -/* A quick'n'dirty lookup table to decode an aLaw-encoded byte sample into a - * signed 16-bit sample */ -constexpr ALshort aLawDecompressionTable[256] = { - -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, - -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, - -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, - -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, - -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, - -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, - -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, - -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, - -344, -328, -376, -360, -280, -264, -312, -296, - -472, -456, -504, -488, -408, -392, -440, -424, - -88, -72, -120, -104, -24, -8, -56, -40, - -216, -200, -248, -232, -152, -136, -184, -168, - -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, - -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, - -688, -656, -752, -720, -560, -528, -624, -592, - -944, -912, -1008, -976, -816, -784, -880, -848, - 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, - 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, - 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, - 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, - 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, - 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, - 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, - 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, - 344, 328, 376, 360, 280, 264, 312, 296, - 472, 456, 504, 488, 408, 392, 440, 424, - 88, 72, 120, 104, 24, 8, 56, 40, - 216, 200, 248, 232, 152, 136, 184, 168, - 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, - 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, - 688, 656, 752, 720, 560, 528, 624, 592, - 944, 912, 1008, 976, 816, 784, 880, 848 -}; - -template<FmtType T> -struct FmtTypeTraits { }; - -template<> -struct FmtTypeTraits<FmtUByte> { - using Type = ALubyte; - static constexpr inline float to_float(const Type val) noexcept - { return val*(1.0f/128.0f) - 1.0f; } -}; -template<> -struct FmtTypeTraits<FmtShort> { - using Type = ALshort; - static constexpr inline float to_float(const Type val) noexcept { return val*(1.0f/32768.0f); } -}; -template<> -struct FmtTypeTraits<FmtFloat> { - using Type = ALfloat; - static constexpr inline float to_float(const Type val) noexcept { return val; } -}; -template<> -struct FmtTypeTraits<FmtDouble> { - using Type = ALdouble; - static constexpr inline float to_float(const Type val) noexcept - { return static_cast<ALfloat>(val); } -}; -template<> -struct FmtTypeTraits<FmtMulaw> { - using Type = ALubyte; - static constexpr inline float to_float(const Type val) noexcept - { return muLawDecompressionTable[val] * (1.0f/32768.0f); } -}; -template<> -struct FmtTypeTraits<FmtAlaw> { - using Type = ALubyte; - static constexpr inline float to_float(const Type val) noexcept - { return aLawDecompressionTable[val] * (1.0f/32768.0f); } -}; - - -void SendSourceStoppedEvent(ALCcontext *context, ALuint 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 = AL_STOPPED; - - ring->writeAdvance(1); - context->mEventSem.post(); -} - - -const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter, ALfloat *dst, - const ALfloat *src, const size_t numsamples, int type) -{ - switch(type) - { - case AF_None: - lpfilter->clear(); - hpfilter->clear(); - break; - - case AF_LowPass: - lpfilter->process(dst, src, numsamples); - hpfilter->clear(); - return dst; - case AF_HighPass: - lpfilter->clear(); - hpfilter->process(dst, src, numsamples); - return dst; - - case AF_BandPass: - lpfilter->process(dst, src, numsamples); - hpfilter->process(dst, dst, numsamples); - return dst; - } - return src; -} - - -template<FmtType T> -inline void LoadSampleArray(ALfloat *RESTRICT dst, const al::byte *src, const size_t srcstep, - const size_t samples) noexcept -{ - using SampleType = typename FmtTypeTraits<T>::Type; - - const SampleType *RESTRICT ssrc{reinterpret_cast<const SampleType*>(src)}; - for(size_t i{0u};i < samples;i++) - dst[i] = FmtTypeTraits<T>::to_float(ssrc[i*srcstep]); -} - -void LoadSamples(ALfloat *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, - const size_t samples) noexcept -{ -#define HANDLE_FMT(T) case T: LoadSampleArray<T>(dst, src, srcstep, samples); break - switch(srctype) - { - HANDLE_FMT(FmtUByte); - HANDLE_FMT(FmtShort); - HANDLE_FMT(FmtFloat); - HANDLE_FMT(FmtDouble); - HANDLE_FMT(FmtMulaw); - HANDLE_FMT(FmtAlaw); - } -#undef HANDLE_FMT -} - -ALfloat *LoadBufferStatic(ALbufferlistitem *BufferListItem, ALbufferlistitem *&BufferLoopItem, - const size_t NumChannels, const size_t SampleSize, const size_t chan, size_t DataPosInt, - al::span<ALfloat> SrcBuffer) -{ - const ALbuffer *Buffer{BufferListItem->mBuffer}; - const ALuint LoopStart{Buffer->LoopStart}; - const ALuint LoopEnd{Buffer->LoopEnd}; - 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->SampleLen-DataPosInt)}; - - const al::byte *Data{Buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, 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->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataRem); - SrcBuffer = SrcBuffer.subspan(DataRem); - - /* Load any repeats of the loop we can to fill the buffer. */ - const auto LoopSize = static_cast<size_t>(LoopEnd - LoopStart); - while(!SrcBuffer.empty()) - { - const size_t DataSize{minz(SrcBuffer.size(), LoopSize)}; - - Data = Buffer->mData.data() + (LoopStart*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataSize); - SrcBuffer = SrcBuffer.subspan(DataSize); - } - } - return SrcBuffer.begin(); -} - -ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, - const size_t NumChannels, const size_t SampleSize, const size_t chan, size_t DataPosInt, - al::span<ALfloat> SrcBuffer) -{ - /* Crawl the buffer queue to fill in the temp buffer */ - while(BufferListItem && !SrcBuffer.empty()) - { - ALbuffer *Buffer{BufferListItem->mBuffer}; - if(!(Buffer && DataPosInt < Buffer->SampleLen)) - { - if(Buffer) DataPosInt -= Buffer->SampleLen; - BufferListItem = BufferListItem->mNext.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - continue; - } - - const size_t DataSize{minz(SrcBuffer.size(), Buffer->SampleLen-DataPosInt)}; - - const al::byte *Data{Buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataSize); - SrcBuffer = SrcBuffer.subspan(DataSize); - if(SrcBuffer.empty()) break; - - DataPosInt = 0; - BufferListItem = BufferListItem->mNext.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - } - - return SrcBuffer.begin(); -} - - -void DoHrtfMix(const float TargetGain, DirectParams &parms, const float *samples, - const ALuint DstBufferSize, const ALuint Counter, ALuint OutPos, const ALuint IrSize, - ALCdevice *Device) -{ - 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.State.History.begin(), parms.Hrtf.State.History.end(), - std::begin(HrtfSamples)); - std::copy_n(samples, DstBufferSize, src_iter); - /* Copy the last used samples back into the history buffer for later. */ - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.State.History.size(), - parms.Hrtf.State.History.begin()); - - /* If fading, the old gain is not silence, and this is the first mixing - * pass, fade between the IRs. - */ - ALuint fademix{0u}; - if(Counter && parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD && OutPos == 0) - { - fademix = minu(DstBufferSize, 128); - - 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 LIKELY(Counter > fademix) - { - const ALfloat a{static_cast<float>(fademix) / static_cast<float>(Counter)}; - gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a); - } - MixHrtfFilter hrtfparams; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0]; - hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1]; - hrtfparams.Gain = 0.0f; - hrtfparams.GainStep = gain / static_cast<float>(fademix); - - MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams, - fademix); - /* Update the old parameters with the result. */ - parms.Hrtf.Old = parms.Hrtf.Target; - if(fademix < Counter) - parms.Hrtf.Old.Gain = hrtfparams.Gain; - else - parms.Hrtf.Old.Gain = TargetGain; - OutPos += fademix; - } - - if LIKELY(fademix < DstBufferSize) - { - const ALuint 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<float>(todo) / static_cast<float>(Counter-fademix)}; - gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a); - } - - MixHrtfFilter hrtfparams; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0]; - hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1]; - hrtfparams.Gain = parms.Hrtf.Old.Gain; - hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo); - MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo); - /* Store the interpolated gain or the final target gain depending if - * the fade is done. - */ - if(DstBufferSize < Counter) - parms.Hrtf.Old.Gain = gain; - else - parms.Hrtf.Old.Gain = TargetGain; - } -} - -void DoNfcMix(ALvoice::TargetData &Direct, const float *TargetGains, DirectParams &parms, - const float *samples, const ALuint DstBufferSize, const ALuint Counter, const ALuint OutPos, - ALCdevice *Device) -{ - const size_t outcount{Device->NumChannelsPerOrder[0]}; - MixSamples({samples, DstBufferSize}, Direct.Buffer.first(outcount), - parms.Gains.Current.data(), TargetGains, Counter, OutPos); - - const al::span<float> nfcsamples{Device->NfcSampleData, DstBufferSize}; - size_t chanoffset{outcount}; - using FilterProc = void (NfcFilter::*)(float*,const float*,const size_t); - auto apply_nfc = [&Direct,&parms,samples,TargetGains,Counter,OutPos,&chanoffset,nfcsamples]( - const FilterProc process, const size_t chancount) -> void - { - if(chancount < 1) return; - (parms.NFCtrlFilter.*process)(nfcsamples.data(), samples, nfcsamples.size()); - MixSamples(nfcsamples, Direct.Buffer.subspan(chanoffset, chancount), - &parms.Gains.Current[chanoffset], &TargetGains[chanoffset], Counter, OutPos); - chanoffset += chancount; - }; - apply_nfc(&NfcFilter::process1, Device->NumChannelsPerOrder[1]); - apply_nfc(&NfcFilter::process2, Device->NumChannelsPerOrder[2]); - apply_nfc(&NfcFilter::process3, Device->NumChannelsPerOrder[3]); -} - -} // namespace - -void ALvoice::mix(const State vstate, ALCcontext *Context, const ALuint SamplesToDo) -{ - static constexpr std::array<float,MAX_OUTPUT_CHANNELS> SilentTarget{}; - - ASSUME(SamplesToDo > 0); - - /* Get voice info */ - const bool isstatic{(mFlags&VOICE_IS_STATIC) != 0}; - ALuint DataPosInt{mPosition.load(std::memory_order_relaxed)}; - ALuint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; - const ALuint NumChannels{mNumChannels}; - const ALuint SampleSize{mSampleSize}; - const ALuint increment{mStep}; - if(increment < 1) return; - - ASSUME(NumChannels > 0); - ASSUME(SampleSize > 0); - ASSUME(increment > 0); - - ALCdevice *Device{Context->mDevice.get()}; - const ALuint NumSends{Device->NumAuxSends}; - const ALuint IrSize{Device->mHrtf ? Device->mHrtf->irSize : 0}; - - ResamplerFunc Resample{(increment == FRACTIONONE && DataPosFrac == 0) ? - Resample_<CopyTag,CTag> : mResampler}; - - ALuint Counter{(mFlags&VOICE_IS_FADING) ? SamplesToDo : 0}; - if(!Counter) - { - /* No fading, just overwrite the old/current params. */ - for(ALuint chan{0};chan < NumChannels;chan++) - { - ChannelData &chandata = mChans[chan]; - { - DirectParams &parms = chandata.mDryParams; - if(!(mFlags&VOICE_HAS_HRTF)) - parms.Gains.Current = parms.Gains.Target; - else - parms.Hrtf.Old = parms.Hrtf.Target; - } - for(ALuint send{0};send < NumSends;++send) - { - if(mSend[send].Buffer.empty()) - continue; - - SendParams &parms = chandata.mWetParams[send]; - parms.Gains.Current = parms.Gains.Target; - } - } - } - else if((mFlags&VOICE_HAS_HRTF)) - { - for(ALuint chan{0};chan < NumChannels;chan++) - { - DirectParams &parms = mChans[chan].mDryParams; - if(!(parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD)) - { - /* The old HRTF params are silent, so overwrite the old - * coefficients with the new, and reset the old gain to 0. The - * future mix will then fade from silence. - */ - parms.Hrtf.Old = parms.Hrtf.Target; - parms.Hrtf.Old.Gain = 0.0f; - } - } - } - - ALuint buffers_done{0u}; - ALuint OutPos{0u}; - do { - /* Figure out how many buffer samples will be needed */ - ALuint DstBufferSize{SamplesToDo - OutPos}; - - /* Calculate the last written dst sample pos. */ - uint64_t DataSize64{DstBufferSize - 1}; - /* Calculate the last read src sample pos. */ - DataSize64 = (DataSize64*increment + DataPosFrac) >> FRACTIONBITS; - /* +1 to get the src sample count, include padding. */ - DataSize64 += 1 + MAX_RESAMPLER_PADDING; - - auto SrcBufferSize = static_cast<ALuint>( - minu64(DataSize64, BUFFERSIZE + MAX_RESAMPLER_PADDING + 1)); - if(SrcBufferSize > BUFFERSIZE + MAX_RESAMPLER_PADDING) - { - SrcBufferSize = BUFFERSIZE + MAX_RESAMPLER_PADDING; - /* If the source buffer got saturated, we can't fill the desired - * dst size. Figure out how many samples we can actually mix from - * this. - */ - DataSize64 = SrcBufferSize - MAX_RESAMPLER_PADDING; - DataSize64 = ((DataSize64<<FRACTIONBITS) - DataPosFrac + increment-1) / increment; - DstBufferSize = static_cast<ALuint>(minu64(DataSize64, DstBufferSize)); - - /* Some mixers like having a multiple of 4, so try to give that - * unless this is the last update. - */ - if(DstBufferSize < SamplesToDo-OutPos) - DstBufferSize &= ~3u; - } - - ASSUME(DstBufferSize > 0); - for(ALuint chan{0};chan < NumChannels;chan++) - { - ChannelData &chandata = mChans[chan]; - const al::span<ALfloat> 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(), MAX_RESAMPLER_PADDING>>1, - SrcData.begin()); - - if UNLIKELY(!BufferListItem) - srciter = std::copy(chandata.mPrevSamples.begin()+(MAX_RESAMPLER_PADDING>>1), - chandata.mPrevSamples.end(), srciter); - else if(isstatic) - srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, NumChannels, - SampleSize, chan, DataPosInt, {srciter, SrcData.end()}); - else - srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, NumChannels, - SampleSize, chan, DataPosInt, {srciter, SrcData.end()}); - - if UNLIKELY(srciter != SrcData.end()) - { - /* If the source buffer wasn't filled, copy the last sample for - * the remaining buffer. Ideally it should have ended with - * silence, but if not the gain fading should help avoid clicks - * from sudden amplitude changes. - */ - const ALfloat sample{*(srciter-1)}; - std::fill(srciter, SrcData.end(), sample); - } - - /* Store the last source samples used for next time. */ - std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS], - chandata.mPrevSamples.size(), chandata.mPrevSamples.begin()); - - /* Resample, then apply ambisonic upsampling as needed. */ - const ALfloat *ResampledData{Resample(&mResampleState, - &SrcData[MAX_RESAMPLER_PADDING>>1], DataPosFrac, increment, - {Device->ResampledData, DstBufferSize})}; - if((mFlags&VOICE_IS_AMBISONIC)) - { - const ALfloat hfscale{chandata.mAmbiScale}; - /* Beware the evil const_cast. It's safe since it's pointing to - * either SourceData or ResampledData (both non-const), but the - * resample method takes the source as const float* and may - * return it without copying to output, making it currently - * unavoidable. - */ - chandata.mAmbiSplitter.applyHfScale(const_cast<ALfloat*>(ResampledData), hfscale, - DstBufferSize); - } - - /* Now filter and mix to the appropriate outputs. */ - ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData; - { - DirectParams &parms = chandata.mDryParams; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, FilterBuf, - ResampledData, DstBufferSize, mDirect.FilterType)}; - - if((mFlags&VOICE_HAS_HRTF)) - { - const ALfloat TargetGain{UNLIKELY(vstate == ALvoice::Stopping) ? 0.0f : - parms.Hrtf.Target.Gain}; - DoHrtfMix(TargetGain, parms, samples, DstBufferSize, Counter, OutPos, IrSize, - Device); - } - else if((mFlags&VOICE_HAS_NFC)) - { - const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget.data() : parms.Gains.Target.data()}; - DoNfcMix(mDirect, TargetGains, parms, samples, DstBufferSize, Counter, OutPos, - Device); - } - else - { - const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget.data() : parms.Gains.Target.data()}; - MixSamples({samples, DstBufferSize}, mDirect.Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); - } - } - - for(ALuint send{0};send < NumSends;++send) - { - if(mSend[send].Buffer.empty()) - continue; - - SendParams &parms = chandata.mWetParams[send]; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, FilterBuf, - ResampledData, DstBufferSize, mSend[send].FilterType)}; - - const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget.data() : parms.Gains.Target.data()}; - MixSamples({samples, DstBufferSize}, mSend[send].Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); - } - } - /* Update positions */ - DataPosFrac += increment*DstBufferSize; - DataPosInt += DataPosFrac>>FRACTIONBITS; - DataPosFrac &= FRACTIONMASK; - - OutPos += DstBufferSize; - Counter = maxu(DstBufferSize, Counter) - DstBufferSize; - - if UNLIKELY(!BufferListItem) - { - /* Do nothing extra when there's no buffers. */ - } - else if(isstatic) - { - if(BufferLoopItem) - { - /* Handle looping static source */ - const ALbuffer *Buffer{BufferListItem->mBuffer}; - const ALuint LoopStart{Buffer->LoopStart}; - const ALuint LoopEnd{Buffer->LoopEnd}; - 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 - { - /* 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 |= VOICE_IS_FADING; - - /* Don't update positions and buffers if we were stopping. */ - if UNLIKELY(vstate == ALvoice::Stopping) - { - mPlayState.store(ALvoice::Stopped, std::memory_order_release); - return; - } - - /* Capture the source ID in case it's reset for stopping. */ - const ALuint 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 ALbitfieldSOFT 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); - Context->mEventSem.post(); - } - } - - 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(ALvoice::Stopping, std::memory_order_release); - if((enabledevt&EventType_SourceStateChange)) - SendSourceStoppedEvent(Context, SourceID); - } -} diff --git a/alc/voice.h b/alc/voice.h deleted file mode 100644 index d6b624f9..00000000 --- a/alc/voice.h +++ /dev/null @@ -1,293 +0,0 @@ -#ifndef VOICE_H -#define VOICE_H - -#include <array> - -#include "AL/al.h" -#include "AL/alext.h" - -#include "al/buffer.h" -#include "alspan.h" -#include "alu.h" -#include "filters/biquad.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "hrtf.h" - -enum class DistanceModel; - - -enum SpatializeMode { - SpatializeOff = AL_FALSE, - SpatializeOn = AL_TRUE, - SpatializeAuto = AL_AUTO_SOFT -}; - -enum class Resampler { - Point, - Linear, - Cubic, - FastBSinc12, - BSinc12, - FastBSinc24, - BSinc24, - - Max = BSinc24 -}; -extern Resampler ResamplerDefault; - -/* The number of distinct scale and phase intervals within the bsinc filter - * table. - */ -#define BSINC_SCALE_BITS 4 -#define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS) -#define BSINC_PHASE_BITS 5 -#define BSINC_PHASE_COUNT (1<<BSINC_PHASE_BITS) - -/* Interpolator state. Kind of a misnomer since the interpolator itself is - * stateless. This just keeps it from having to recompute scale-related - * mappings for every sample. - */ -struct BsincState { - float sf; /* Scale interpolation factor. */ - ALuint m; /* Coefficient count. */ - ALuint l; /* Left coefficient offset. */ - /* Filter coefficients, followed by the phase, scale, and scale-phase - * delta coefficients. Starting at phase index 0, each subsequent phase - * index follows contiguously. - */ - const float *filter; -}; - -union InterpState { - BsincState bsinc; -}; - -using ResamplerFunc = const float*(*)(const InterpState *state, const float *RESTRICT src, - ALuint frac, ALuint increment, const al::span<float> dst); - -ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state); - - -enum { - AF_None = 0, - AF_LowPass = 1, - AF_HighPass = 2, - AF_BandPass = AF_LowPass | AF_HighPass -}; - - -struct MixHrtfFilter { - const HrirArray *Coeffs; - ALsizei Delay[2]; - float Gain; - float GainStep; -}; - - -struct DirectParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - NfcFilter NFCtrlFilter; - - struct { - HrtfFilter Old; - HrtfFilter Target; - HrtfState State; - } Hrtf; - - struct { - std::array<float,MAX_OUTPUT_CHANNELS> Current; - std::array<float,MAX_OUTPUT_CHANNELS> Target; - } Gains; -}; - -struct SendParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - struct { - std::array<float,MAX_OUTPUT_CHANNELS> Current; - std::array<float,MAX_OUTPUT_CHANNELS> Target; - } Gains; -}; - - -struct ALvoicePropsBase { - float Pitch; - float Gain; - float OuterGain; - float MinGain; - float MaxGain; - float InnerAngle; - float OuterAngle; - float RefDistance; - float MaxDistance; - float RolloffFactor; - std::array<float,3> Position; - std::array<float,3> Velocity; - std::array<float,3> Direction; - std::array<float,3> OrientAt; - std::array<float,3> OrientUp; - bool HeadRelative; - DistanceModel mDistanceModel; - Resampler mResampler; - bool DirectChannels; - SpatializeMode mSpatializeMode; - - bool DryGainHFAuto; - bool WetGainAuto; - bool WetGainHFAuto; - float OuterGainHF; - - float AirAbsorptionFactor; - float RoomRolloffFactor; - float DopplerFactor; - - std::array<float,2> StereoPan; - - float Radius; - - /** Direct filter and auxiliary send info. */ - struct { - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; - } Send[MAX_SENDS]; -}; - -struct ALvoiceProps : public ALvoicePropsBase { - std::atomic<ALvoiceProps*> next{nullptr}; - - DEF_NEWDEL(ALvoiceProps) -}; - -#define VOICE_IS_STATIC (1u<<0) -#define VOICE_IS_FADING (1u<<1) /* Fading sources use gain stepping for smooth transitions. */ -#define VOICE_IS_AMBISONIC (1u<<2) /* Voice needs HF scaling for ambisonic upsampling. */ -#define VOICE_HAS_HRTF (1u<<3) -#define VOICE_HAS_NFC (1u<<4) - -struct ALvoice { - enum State { - Stopped = 0, - Playing = 1, - Stopping = 2 - }; - - std::atomic<ALvoiceProps*> mUpdate{nullptr}; - - std::atomic<ALuint> mSourceID{0u}; - std::atomic<State> mPlayState{Stopped}; - - ALvoicePropsBase mProps; - - /** - * Source offset in samples, relative to the currently playing buffer, NOT - * the whole queue. - */ - std::atomic<ALuint> mPosition; - /** Fractional (fixed-point) offset to the next sample. */ - std::atomic<ALuint> mPositionFrac; - - /* Current buffer queue item being played. */ - std::atomic<ALbufferlistitem*> mCurrentBuffer; - - /* Buffer queue item to loop to at end of queue (will be NULL for non- - * looping voices). - */ - std::atomic<ALbufferlistitem*> mLoopBuffer; - - /* Properties for the attached buffer(s). */ - FmtChannels mFmtChannels; - ALuint mFrequency; - ALuint mNumChannels; - ALuint mSampleSize; - - /** Current target parameters used for mixing. */ - ALuint mStep; - - ResamplerFunc mResampler; - - InterpState mResampleState; - - ALuint mFlags; - - struct TargetData { - int FilterType; - al::span<FloatBufferLine> Buffer; - }; - TargetData mDirect; - std::array<TargetData,MAX_SENDS> mSend; - - struct ChannelData { - alignas(16) std::array<float,MAX_RESAMPLER_PADDING> mPrevSamples; - - float mAmbiScale; - BandSplitter mAmbiSplitter; - - DirectParams mDryParams; - std::array<SendParams,MAX_SENDS> mWetParams; - }; - std::array<ChannelData,MAX_INPUT_CHANNELS> mChans; - - ALvoice() = default; - ALvoice(const ALvoice&) = delete; - ALvoice(ALvoice&& rhs) noexcept { *this = std::move(rhs); } - ~ALvoice() { delete mUpdate.exchange(nullptr, std::memory_order_acq_rel); } - ALvoice& operator=(const ALvoice&) = delete; - ALvoice& operator=(ALvoice&& rhs) noexcept - { - ALvoiceProps *old_update{mUpdate.load(std::memory_order_relaxed)}; - mUpdate.store(rhs.mUpdate.exchange(old_update, std::memory_order_relaxed), - std::memory_order_relaxed); - - mSourceID.store(rhs.mSourceID.load(std::memory_order_relaxed), std::memory_order_relaxed); - mPlayState.store(rhs.mPlayState.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - mProps = rhs.mProps; - - mPosition.store(rhs.mPosition.load(std::memory_order_relaxed), std::memory_order_relaxed); - mPositionFrac.store(rhs.mPositionFrac.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - mCurrentBuffer.store(rhs.mCurrentBuffer.load(std::memory_order_relaxed), - std::memory_order_relaxed); - mLoopBuffer.store(rhs.mLoopBuffer.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - mFmtChannels = rhs.mFmtChannels; - mFrequency = rhs.mFrequency; - mNumChannels = rhs.mNumChannels; - mSampleSize = rhs.mSampleSize; - - mStep = rhs.mStep; - mResampler = rhs.mResampler; - - mResampleState = rhs.mResampleState; - - mFlags = rhs.mFlags; - - mDirect = rhs.mDirect; - mSend = rhs.mSend; - mChans = rhs.mChans; - - return *this; - } - - void mix(const State vstate, ALCcontext *Context, const ALuint SamplesToDo); -}; - -#endif /* VOICE_H */ |