aboutsummaryrefslogtreecommitdiffstats
path: root/alc
diff options
context:
space:
mode:
Diffstat (limited to 'alc')
-rw-r--r--alc/alc.cpp3072
-rw-r--r--alc/alcmain.h390
-rw-r--r--alc/alconfig.cpp237
-rw-r--r--alc/alconfig.h4
-rw-r--r--alc/alcontext.h196
-rw-r--r--alc/alu.cpp2314
-rw-r--r--alc/alu.h152
-rw-r--r--alc/ambdec.cpp434
-rw-r--r--alc/ambdec.h48
-rw-r--r--alc/ambidefs.h120
-rw-r--r--alc/backends/alsa.cpp351
-rw-r--r--alc/backends/alsa.h6
-rw-r--r--alc/backends/base.cpp184
-rw-r--r--alc/backends/base.h104
-rw-r--r--alc/backends/coreaudio.cpp753
-rw-r--r--alc/backends/coreaudio.h6
-rw-r--r--alc/backends/dsound.cpp486
-rw-r--r--alc/backends/dsound.h6
-rw-r--r--alc/backends/jack.cpp596
-rw-r--r--alc/backends/jack.h6
-rw-r--r--alc/backends/loopback.cpp25
-rw-r--r--alc/backends/loopback.h6
-rw-r--r--alc/backends/null.cpp57
-rw-r--r--alc/backends/null.h6
-rw-r--r--alc/backends/oboe.cpp360
-rw-r--r--alc/backends/oboe.h19
-rw-r--r--alc/backends/opensl.cpp373
-rw-r--r--alc/backends/opensl.h6
-rw-r--r--alc/backends/oss.cpp284
-rw-r--r--alc/backends/oss.h6
-rw-r--r--alc/backends/pipewire.cpp2166
-rw-r--r--alc/backends/pipewire.h23
-rw-r--r--alc/backends/portaudio.cpp148
-rw-r--r--alc/backends/portaudio.h6
-rw-r--r--alc/backends/pulseaudio.cpp1028
-rw-r--r--alc/backends/pulseaudio.h6
-rw-r--r--alc/backends/qsa.cpp963
-rw-r--r--alc/backends/qsa.h19
-rw-r--r--alc/backends/sdl2.cpp135
-rw-r--r--alc/backends/sdl2.h6
-rw-r--r--alc/backends/sndio.cpp456
-rw-r--r--alc/backends/sndio.h6
-rw-r--r--alc/backends/solaris.cpp180
-rw-r--r--alc/backends/solaris.h6
-rw-r--r--alc/backends/wasapi.cpp1476
-rw-r--r--alc/backends/wasapi.h6
-rw-r--r--alc/backends/wave.cpp239
-rw-r--r--alc/backends/wave.h6
-rw-r--r--alc/backends/winmm.cpp216
-rw-r--r--alc/backends/winmm.h6
-rw-r--r--alc/bformatdec.cpp203
-rw-r--r--alc/bformatdec.h62
-rw-r--r--alc/bs2b.cpp183
-rw-r--r--alc/bs2b.h89
-rw-r--r--alc/compat.h9
-rw-r--r--alc/context.cpp1105
-rw-r--r--alc/context.h540
-rw-r--r--alc/converter.cpp360
-rw-r--r--alc/converter.h61
-rw-r--r--alc/cpu_caps.h16
-rw-r--r--alc/devformat.h121
-rw-r--r--alc/device.cpp93
-rw-r--r--alc/device.h165
-rw-r--r--alc/effects/autowah.cpp258
-rw-r--r--alc/effects/base.h171
-rw-r--r--alc/effects/chorus.cpp556
-rw-r--r--alc/effects/compressor.cpp192
-rw-r--r--alc/effects/convolution.cpp636
-rw-r--r--alc/effects/dedicated.cpp121
-rw-r--r--alc/effects/distortion.cpp204
-rw-r--r--alc/effects/echo.cpp191
-rw-r--r--alc/effects/equalizer.cpp294
-rw-r--r--alc/effects/fshifter.cpp321
-rw-r--r--alc/effects/modulator.cpp235
-rw-r--r--alc/effects/null.cpp117
-rw-r--r--alc/effects/pshifter.cpp444
-rw-r--r--alc/effects/reverb.cpp2250
-rw-r--r--alc/effects/vmorpher.cpp360
-rw-r--r--alc/filters/biquad.cpp126
-rw-r--r--alc/filters/biquad.h113
-rw-r--r--alc/filters/nfc.cpp391
-rw-r--r--alc/filters/nfc.h61
-rw-r--r--alc/filters/splitter.cpp117
-rw-r--r--alc/filters/splitter.h34
-rw-r--r--alc/fpu_modes.h25
-rw-r--r--alc/helpers.cpp649
-rw-r--r--alc/hrtf.cpp1424
-rw-r--r--alc/hrtf.h115
-rw-r--r--alc/inprogext.h75
-rw-r--r--alc/logging.h59
-rw-r--r--alc/mastering.cpp468
-rw-r--r--alc/mastering.h103
-rw-r--r--alc/mixer/defs.h68
-rw-r--r--alc/mixer/hrtfbase.h119
-rw-r--r--alc/mixer/mixer_c.cpp219
-rw-r--r--alc/mixer/mixer_neon.cpp324
-rw-r--r--alc/mixer/mixer_sse.cpp297
-rw-r--r--alc/mixer/mixer_sse2.cpp83
-rw-r--r--alc/mixer/mixer_sse3.cpp0
-rw-r--r--alc/mixer/mixer_sse41.cpp88
-rw-r--r--alc/panning.cpp1511
-rw-r--r--alc/ringbuffer.cpp251
-rw-r--r--alc/ringbuffer.h97
-rw-r--r--alc/uhjfilter.cpp138
-rw-r--r--alc/uhjfilter.h54
-rw-r--r--alc/uiddefs.cpp37
-rw-r--r--alc/voice.cpp837
-rw-r--r--alc/voice.h293
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 &params) -> 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 &params) -> 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);
}
diff --git a/alc/alu.h b/alc/alu.h
index b3cfd840..67fd09e5 100644
--- a/alc/alu.h
+++ b/alc/alu.h
@@ -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, &registryEvents, 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, &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;
+ });
+
+ /* 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, &params, 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, &param);
- param.sched_priority=param.sched_curpriority+1;
- SchedSet(0, 0, SCHED_NOCHANGE, &param);
-
- 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, &regs[0], &regs[1], &regs[2], &regs[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, &param);
- }
-#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 */