diff options
author | Chris Robinson <[email protected]> | 2019-07-28 18:56:04 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2019-07-28 18:56:04 -0700 |
commit | cb3e96e75640730b9391f0d2d922eecd9ee2ce79 (patch) | |
tree | 23520551bddb2a80354e44da47f54201fdc084f0 /Alc | |
parent | 93e60919c8f387c36c267ca9faa1ac653254aea6 (diff) |
Rename Alc to alc
Diffstat (limited to 'Alc')
97 files changed, 0 insertions, 35896 deletions
diff --git a/Alc/alc.cpp b/Alc/alc.cpp deleted file mode 100644 index 00f90d91..00000000 --- a/Alc/alc.cpp +++ /dev/null @@ -1,4342 +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 "version.h" - -#include <exception> -#include <algorithm> -#include <array> -#include <atomic> -#include <cctype> -#include <chrono> -#include <climits> -#include <cmath> -#include <csignal> -#include <cstdint> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <functional> -#include <iterator> -#include <limits> -#include <memory> -#include <mutex> -#include <new> -#include <numeric> -#include <string> -#include <thread> -#include <utility> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" -#include "AL/efx.h" - -#include "alAuxEffectSlot.h" -#include "alcmain.h" -#include "alEffect.h" -#include "alError.h" -#include "alFilter.h" -#include "alListener.h" -#include "alSource.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 "alu.h" -#include "ambidefs.h" -#include "atomic.h" -#include "bformatdec.h" -#include "bs2b.h" -#include "compat.h" -#include "cpu_caps.h" -#include "effects/base.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "fpu_modes.h" -#include "hrtf.h" -#include "inprogext.h" -#include "logging.h" -#include "mastering.h" -#include "opthelpers.h" -#include "ringbuffer.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_JACK -#include "backends/jack.h" -#endif -#ifdef HAVE_PULSEAUDIO -#include "backends/pulseaudio.h" -#endif -#ifdef HAVE_ALSA -#include "backends/alsa.h" -#endif -#ifdef HAVE_WASAPI -#include "backends/wasapi.h" -#endif -#ifdef HAVE_COREAUDIO -#include "backends/coreaudio.h" -#endif -#ifdef HAVE_OPENSL -#include "backends/opensl.h" -#endif -#ifdef HAVE_SOLARIS -#include "backends/solaris.h" -#endif -#ifdef HAVE_SNDIO -#include "backends/sndio.h" -#endif -#ifdef HAVE_OSS -#include "backends/oss.h" -#endif -#ifdef HAVE_QSA -#include "backends/qsa.h" -#endif -#ifdef HAVE_DSOUND -#include "backends/dsound.h" -#endif -#ifdef HAVE_WINMM -#include "backends/winmm.h" -#endif -#ifdef HAVE_PORTAUDIO -#include "backends/portaudio.h" -#endif -#ifdef HAVE_SDL2 -#include "backends/sdl2.h" -#endif -#ifdef HAVE_WAVE -#include "backends/wave.h" -#endif - - -namespace { - -using namespace std::placeholders; -using std::chrono::seconds; -using std::chrono::nanoseconds; - - -/************************************************ - * Backends - ************************************************/ -struct BackendInfo { - const char *name; - BackendFactory& (*getFactory)(void); -}; - -BackendInfo BackendList[] = { -#ifdef HAVE_JACK - { "jack", JackBackendFactory::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_OPENSL - { "opensl", OSLBackendFactory::getFactory }, -#endif -#ifdef HAVE_SOLARIS - { "solaris", SolarisBackendFactory::getFactory }, -#endif -#ifdef HAVE_SNDIO - { "sndio", SndIOBackendFactory::getFactory }, -#endif -#ifdef HAVE_OSS - { "oss", OSSBackendFactory::getFactory }, -#endif -#ifdef HAVE_QSA - { "qsa", QSABackendFactory::getFactory }, -#endif -#ifdef HAVE_DSOUND - { "dsound", DSoundBackendFactory::getFactory }, -#endif -#ifdef HAVE_WINMM - { "winmm", WinMMBackendFactory::getFactory }, -#endif -#ifdef HAVE_PORTAUDIO - { "port", PortBackendFactory::getFactory }, -#endif -#ifdef HAVE_SDL2 - { "sdl2", SDL2BackendFactory::getFactory }, -#endif - - { "null", NullBackendFactory::getFactory }, -#ifdef HAVE_WAVE - { "wave", WaveBackendFactory::getFactory }, -#endif -}; -auto BackendListEnd = std::end(BackendList); - -BackendFactory *PlaybackFactory{}; -BackendFactory *CaptureFactory{}; - - -/************************************************ - * Functions, enums, and errors - ************************************************/ -#define DECL(x) { #x, (ALCvoid*)(x) } -const struct { - const ALCchar *funcName; - ALCvoid *address; -} alcFunctions[] = { - DECL(alcCreateContext), - DECL(alcMakeContextCurrent), - DECL(alcProcessContext), - DECL(alcSuspendContext), - DECL(alcDestroyContext), - DECL(alcGetCurrentContext), - DECL(alcGetContextsDevice), - DECL(alcOpenDevice), - DECL(alcCloseDevice), - DECL(alcGetError), - DECL(alcIsExtensionPresent), - DECL(alcGetProcAddress), - DECL(alcGetEnumValue), - DECL(alcGetString), - DECL(alcGetIntegerv), - DECL(alcCaptureOpenDevice), - DECL(alcCaptureCloseDevice), - DECL(alcCaptureStart), - DECL(alcCaptureStop), - DECL(alcCaptureSamples), - - DECL(alcSetThreadContext), - DECL(alcGetThreadContext), - - DECL(alcLoopbackOpenDeviceSOFT), - DECL(alcIsRenderFormatSupportedSOFT), - DECL(alcRenderSamplesSOFT), - - DECL(alcDevicePauseSOFT), - DECL(alcDeviceResumeSOFT), - - DECL(alcGetStringiSOFT), - DECL(alcResetDeviceSOFT), - - DECL(alcGetInteger64vSOFT), - - DECL(alEnable), - DECL(alDisable), - DECL(alIsEnabled), - DECL(alGetString), - DECL(alGetBooleanv), - DECL(alGetIntegerv), - DECL(alGetFloatv), - DECL(alGetDoublev), - DECL(alGetBoolean), - DECL(alGetInteger), - DECL(alGetFloat), - DECL(alGetDouble), - DECL(alGetError), - DECL(alIsExtensionPresent), - DECL(alGetProcAddress), - DECL(alGetEnumValue), - DECL(alListenerf), - DECL(alListener3f), - DECL(alListenerfv), - DECL(alListeneri), - DECL(alListener3i), - DECL(alListeneriv), - DECL(alGetListenerf), - DECL(alGetListener3f), - DECL(alGetListenerfv), - DECL(alGetListeneri), - DECL(alGetListener3i), - DECL(alGetListeneriv), - DECL(alGenSources), - DECL(alDeleteSources), - DECL(alIsSource), - DECL(alSourcef), - DECL(alSource3f), - DECL(alSourcefv), - DECL(alSourcei), - DECL(alSource3i), - DECL(alSourceiv), - DECL(alGetSourcef), - DECL(alGetSource3f), - DECL(alGetSourcefv), - DECL(alGetSourcei), - DECL(alGetSource3i), - DECL(alGetSourceiv), - DECL(alSourcePlayv), - DECL(alSourceStopv), - DECL(alSourceRewindv), - DECL(alSourcePausev), - DECL(alSourcePlay), - DECL(alSourceStop), - DECL(alSourceRewind), - DECL(alSourcePause), - DECL(alSourceQueueBuffers), - DECL(alSourceUnqueueBuffers), - DECL(alGenBuffers), - DECL(alDeleteBuffers), - DECL(alIsBuffer), - DECL(alBufferData), - DECL(alBufferf), - DECL(alBuffer3f), - DECL(alBufferfv), - DECL(alBufferi), - DECL(alBuffer3i), - DECL(alBufferiv), - DECL(alGetBufferf), - DECL(alGetBuffer3f), - DECL(alGetBufferfv), - DECL(alGetBufferi), - DECL(alGetBuffer3i), - DECL(alGetBufferiv), - DECL(alDopplerFactor), - DECL(alDopplerVelocity), - DECL(alSpeedOfSound), - DECL(alDistanceModel), - - DECL(alGenFilters), - DECL(alDeleteFilters), - DECL(alIsFilter), - DECL(alFilteri), - DECL(alFilteriv), - DECL(alFilterf), - DECL(alFilterfv), - DECL(alGetFilteri), - DECL(alGetFilteriv), - DECL(alGetFilterf), - DECL(alGetFilterfv), - DECL(alGenEffects), - DECL(alDeleteEffects), - DECL(alIsEffect), - DECL(alEffecti), - DECL(alEffectiv), - DECL(alEffectf), - DECL(alEffectfv), - DECL(alGetEffecti), - DECL(alGetEffectiv), - DECL(alGetEffectf), - DECL(alGetEffectfv), - DECL(alGenAuxiliaryEffectSlots), - DECL(alDeleteAuxiliaryEffectSlots), - DECL(alIsAuxiliaryEffectSlot), - DECL(alAuxiliaryEffectSloti), - DECL(alAuxiliaryEffectSlotiv), - DECL(alAuxiliaryEffectSlotf), - DECL(alAuxiliaryEffectSlotfv), - DECL(alGetAuxiliaryEffectSloti), - DECL(alGetAuxiliaryEffectSlotiv), - DECL(alGetAuxiliaryEffectSlotf), - DECL(alGetAuxiliaryEffectSlotfv), - - DECL(alDeferUpdatesSOFT), - DECL(alProcessUpdatesSOFT), - - DECL(alSourcedSOFT), - DECL(alSource3dSOFT), - DECL(alSourcedvSOFT), - DECL(alGetSourcedSOFT), - DECL(alGetSource3dSOFT), - DECL(alGetSourcedvSOFT), - DECL(alSourcei64SOFT), - DECL(alSource3i64SOFT), - DECL(alSourcei64vSOFT), - DECL(alGetSourcei64SOFT), - DECL(alGetSource3i64SOFT), - DECL(alGetSourcei64vSOFT), - - DECL(alGetStringiSOFT), - - DECL(alBufferStorageSOFT), - DECL(alMapBufferSOFT), - DECL(alUnmapBufferSOFT), - DECL(alFlushMappedBufferSOFT), - - DECL(alEventControlSOFT), - DECL(alEventCallbackSOFT), - DECL(alGetPointerSOFT), - DECL(alGetPointervSOFT), -}; -#undef DECL - -#define DECL(x) { #x, (x) } -constexpr struct { - const ALCchar *enumName; - ALCenum value; -} alcEnumerations[] = { - DECL(ALC_INVALID), - DECL(ALC_FALSE), - DECL(ALC_TRUE), - - DECL(ALC_MAJOR_VERSION), - DECL(ALC_MINOR_VERSION), - DECL(ALC_ATTRIBUTES_SIZE), - DECL(ALC_ALL_ATTRIBUTES), - DECL(ALC_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_DEVICE_SPECIFIER), - DECL(ALC_ALL_DEVICES_SPECIFIER), - DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), - DECL(ALC_EXTENSIONS), - DECL(ALC_FREQUENCY), - DECL(ALC_REFRESH), - DECL(ALC_SYNC), - DECL(ALC_MONO_SOURCES), - DECL(ALC_STEREO_SOURCES), - DECL(ALC_CAPTURE_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_SAMPLES), - DECL(ALC_CONNECTED), - - DECL(ALC_EFX_MAJOR_VERSION), - DECL(ALC_EFX_MINOR_VERSION), - DECL(ALC_MAX_AUXILIARY_SENDS), - - DECL(ALC_FORMAT_CHANNELS_SOFT), - DECL(ALC_FORMAT_TYPE_SOFT), - - DECL(ALC_MONO_SOFT), - DECL(ALC_STEREO_SOFT), - DECL(ALC_QUAD_SOFT), - DECL(ALC_5POINT1_SOFT), - DECL(ALC_6POINT1_SOFT), - DECL(ALC_7POINT1_SOFT), - DECL(ALC_BFORMAT3D_SOFT), - - DECL(ALC_BYTE_SOFT), - DECL(ALC_UNSIGNED_BYTE_SOFT), - DECL(ALC_SHORT_SOFT), - DECL(ALC_UNSIGNED_SHORT_SOFT), - DECL(ALC_INT_SOFT), - DECL(ALC_UNSIGNED_INT_SOFT), - DECL(ALC_FLOAT_SOFT), - - DECL(ALC_HRTF_SOFT), - DECL(ALC_DONT_CARE_SOFT), - DECL(ALC_HRTF_STATUS_SOFT), - DECL(ALC_HRTF_DISABLED_SOFT), - DECL(ALC_HRTF_ENABLED_SOFT), - DECL(ALC_HRTF_DENIED_SOFT), - DECL(ALC_HRTF_REQUIRED_SOFT), - DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT), - DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT), - DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT), - DECL(ALC_HRTF_SPECIFIER_SOFT), - DECL(ALC_HRTF_ID_SOFT), - - DECL(ALC_AMBISONIC_LAYOUT_SOFT), - DECL(ALC_AMBISONIC_SCALING_SOFT), - DECL(ALC_AMBISONIC_ORDER_SOFT), - DECL(ALC_ACN_SOFT), - DECL(ALC_FUMA_SOFT), - DECL(ALC_N3D_SOFT), - DECL(ALC_SN3D_SOFT), - - DECL(ALC_OUTPUT_LIMITER_SOFT), - - DECL(ALC_NO_ERROR), - DECL(ALC_INVALID_DEVICE), - DECL(ALC_INVALID_CONTEXT), - DECL(ALC_INVALID_ENUM), - DECL(ALC_INVALID_VALUE), - DECL(ALC_OUT_OF_MEMORY), - - - DECL(AL_INVALID), - DECL(AL_NONE), - DECL(AL_FALSE), - DECL(AL_TRUE), - - DECL(AL_SOURCE_RELATIVE), - DECL(AL_CONE_INNER_ANGLE), - DECL(AL_CONE_OUTER_ANGLE), - DECL(AL_PITCH), - DECL(AL_POSITION), - DECL(AL_DIRECTION), - DECL(AL_VELOCITY), - DECL(AL_LOOPING), - DECL(AL_BUFFER), - DECL(AL_GAIN), - DECL(AL_MIN_GAIN), - DECL(AL_MAX_GAIN), - DECL(AL_ORIENTATION), - DECL(AL_REFERENCE_DISTANCE), - DECL(AL_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAIN), - DECL(AL_MAX_DISTANCE), - DECL(AL_SEC_OFFSET), - DECL(AL_SAMPLE_OFFSET), - DECL(AL_BYTE_OFFSET), - DECL(AL_SOURCE_TYPE), - DECL(AL_STATIC), - DECL(AL_STREAMING), - DECL(AL_UNDETERMINED), - DECL(AL_METERS_PER_UNIT), - DECL(AL_LOOP_POINTS_SOFT), - DECL(AL_DIRECT_CHANNELS_SOFT), - - DECL(AL_DIRECT_FILTER), - DECL(AL_AUXILIARY_SEND_FILTER), - DECL(AL_AIR_ABSORPTION_FACTOR), - DECL(AL_ROOM_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAINHF), - DECL(AL_DIRECT_FILTER_GAINHF_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO), - - DECL(AL_SOURCE_STATE), - DECL(AL_INITIAL), - DECL(AL_PLAYING), - DECL(AL_PAUSED), - DECL(AL_STOPPED), - - DECL(AL_BUFFERS_QUEUED), - DECL(AL_BUFFERS_PROCESSED), - - DECL(AL_FORMAT_MONO8), - DECL(AL_FORMAT_MONO16), - DECL(AL_FORMAT_MONO_FLOAT32), - DECL(AL_FORMAT_MONO_DOUBLE_EXT), - DECL(AL_FORMAT_STEREO8), - DECL(AL_FORMAT_STEREO16), - DECL(AL_FORMAT_STEREO_FLOAT32), - DECL(AL_FORMAT_STEREO_DOUBLE_EXT), - DECL(AL_FORMAT_MONO_IMA4), - DECL(AL_FORMAT_STEREO_IMA4), - DECL(AL_FORMAT_MONO_MSADPCM_SOFT), - DECL(AL_FORMAT_STEREO_MSADPCM_SOFT), - DECL(AL_FORMAT_QUAD8_LOKI), - DECL(AL_FORMAT_QUAD16_LOKI), - DECL(AL_FORMAT_QUAD8), - DECL(AL_FORMAT_QUAD16), - DECL(AL_FORMAT_QUAD32), - DECL(AL_FORMAT_51CHN8), - DECL(AL_FORMAT_51CHN16), - DECL(AL_FORMAT_51CHN32), - DECL(AL_FORMAT_61CHN8), - DECL(AL_FORMAT_61CHN16), - DECL(AL_FORMAT_61CHN32), - DECL(AL_FORMAT_71CHN8), - DECL(AL_FORMAT_71CHN16), - DECL(AL_FORMAT_71CHN32), - DECL(AL_FORMAT_REAR8), - DECL(AL_FORMAT_REAR16), - DECL(AL_FORMAT_REAR32), - DECL(AL_FORMAT_MONO_MULAW), - DECL(AL_FORMAT_MONO_MULAW_EXT), - DECL(AL_FORMAT_STEREO_MULAW), - DECL(AL_FORMAT_STEREO_MULAW_EXT), - DECL(AL_FORMAT_QUAD_MULAW), - DECL(AL_FORMAT_51CHN_MULAW), - DECL(AL_FORMAT_61CHN_MULAW), - DECL(AL_FORMAT_71CHN_MULAW), - DECL(AL_FORMAT_REAR_MULAW), - DECL(AL_FORMAT_MONO_ALAW_EXT), - DECL(AL_FORMAT_STEREO_ALAW_EXT), - - DECL(AL_FORMAT_BFORMAT2D_8), - DECL(AL_FORMAT_BFORMAT2D_16), - DECL(AL_FORMAT_BFORMAT2D_FLOAT32), - DECL(AL_FORMAT_BFORMAT2D_MULAW), - DECL(AL_FORMAT_BFORMAT3D_8), - DECL(AL_FORMAT_BFORMAT3D_16), - DECL(AL_FORMAT_BFORMAT3D_FLOAT32), - DECL(AL_FORMAT_BFORMAT3D_MULAW), - - DECL(AL_FREQUENCY), - DECL(AL_BITS), - DECL(AL_CHANNELS), - DECL(AL_SIZE), - DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT), - DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT), - - DECL(AL_SOURCE_RADIUS), - - DECL(AL_STEREO_ANGLES), - - DECL(AL_UNUSED), - DECL(AL_PENDING), - DECL(AL_PROCESSED), - - DECL(AL_NO_ERROR), - DECL(AL_INVALID_NAME), - DECL(AL_INVALID_ENUM), - DECL(AL_INVALID_VALUE), - DECL(AL_INVALID_OPERATION), - DECL(AL_OUT_OF_MEMORY), - - DECL(AL_VENDOR), - DECL(AL_VERSION), - DECL(AL_RENDERER), - DECL(AL_EXTENSIONS), - - DECL(AL_DOPPLER_FACTOR), - DECL(AL_DOPPLER_VELOCITY), - DECL(AL_DISTANCE_MODEL), - DECL(AL_SPEED_OF_SOUND), - DECL(AL_SOURCE_DISTANCE_MODEL), - DECL(AL_DEFERRED_UPDATES_SOFT), - DECL(AL_GAIN_LIMIT_SOFT), - - DECL(AL_INVERSE_DISTANCE), - DECL(AL_INVERSE_DISTANCE_CLAMPED), - DECL(AL_LINEAR_DISTANCE), - DECL(AL_LINEAR_DISTANCE_CLAMPED), - DECL(AL_EXPONENT_DISTANCE), - DECL(AL_EXPONENT_DISTANCE_CLAMPED), - - DECL(AL_FILTER_TYPE), - DECL(AL_FILTER_NULL), - DECL(AL_FILTER_LOWPASS), - DECL(AL_FILTER_HIGHPASS), - DECL(AL_FILTER_BANDPASS), - - DECL(AL_LOWPASS_GAIN), - DECL(AL_LOWPASS_GAINHF), - - DECL(AL_HIGHPASS_GAIN), - DECL(AL_HIGHPASS_GAINLF), - - DECL(AL_BANDPASS_GAIN), - DECL(AL_BANDPASS_GAINHF), - DECL(AL_BANDPASS_GAINLF), - - DECL(AL_EFFECT_TYPE), - DECL(AL_EFFECT_NULL), - DECL(AL_EFFECT_REVERB), - DECL(AL_EFFECT_EAXREVERB), - DECL(AL_EFFECT_CHORUS), - DECL(AL_EFFECT_DISTORTION), - DECL(AL_EFFECT_ECHO), - DECL(AL_EFFECT_FLANGER), - DECL(AL_EFFECT_PITCH_SHIFTER), - DECL(AL_EFFECT_FREQUENCY_SHIFTER), - DECL(AL_EFFECT_VOCAL_MORPHER), - DECL(AL_EFFECT_RING_MODULATOR), - DECL(AL_EFFECT_AUTOWAH), - DECL(AL_EFFECT_COMPRESSOR), - DECL(AL_EFFECT_EQUALIZER), - DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), - DECL(AL_EFFECT_DEDICATED_DIALOGUE), - - DECL(AL_EFFECTSLOT_EFFECT), - DECL(AL_EFFECTSLOT_GAIN), - DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), - DECL(AL_EFFECTSLOT_NULL), - - DECL(AL_EAXREVERB_DENSITY), - DECL(AL_EAXREVERB_DIFFUSION), - DECL(AL_EAXREVERB_GAIN), - DECL(AL_EAXREVERB_GAINHF), - DECL(AL_EAXREVERB_GAINLF), - DECL(AL_EAXREVERB_DECAY_TIME), - DECL(AL_EAXREVERB_DECAY_HFRATIO), - DECL(AL_EAXREVERB_DECAY_LFRATIO), - DECL(AL_EAXREVERB_REFLECTIONS_GAIN), - DECL(AL_EAXREVERB_REFLECTIONS_DELAY), - DECL(AL_EAXREVERB_REFLECTIONS_PAN), - DECL(AL_EAXREVERB_LATE_REVERB_GAIN), - DECL(AL_EAXREVERB_LATE_REVERB_DELAY), - DECL(AL_EAXREVERB_LATE_REVERB_PAN), - DECL(AL_EAXREVERB_ECHO_TIME), - DECL(AL_EAXREVERB_ECHO_DEPTH), - DECL(AL_EAXREVERB_MODULATION_TIME), - DECL(AL_EAXREVERB_MODULATION_DEPTH), - DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_EAXREVERB_HFREFERENCE), - DECL(AL_EAXREVERB_LFREFERENCE), - DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_EAXREVERB_DECAY_HFLIMIT), - - DECL(AL_REVERB_DENSITY), - DECL(AL_REVERB_DIFFUSION), - DECL(AL_REVERB_GAIN), - DECL(AL_REVERB_GAINHF), - DECL(AL_REVERB_DECAY_TIME), - DECL(AL_REVERB_DECAY_HFRATIO), - DECL(AL_REVERB_REFLECTIONS_GAIN), - DECL(AL_REVERB_REFLECTIONS_DELAY), - DECL(AL_REVERB_LATE_REVERB_GAIN), - DECL(AL_REVERB_LATE_REVERB_DELAY), - DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_REVERB_DECAY_HFLIMIT), - - DECL(AL_CHORUS_WAVEFORM), - DECL(AL_CHORUS_PHASE), - DECL(AL_CHORUS_RATE), - DECL(AL_CHORUS_DEPTH), - DECL(AL_CHORUS_FEEDBACK), - DECL(AL_CHORUS_DELAY), - - DECL(AL_DISTORTION_EDGE), - DECL(AL_DISTORTION_GAIN), - DECL(AL_DISTORTION_LOWPASS_CUTOFF), - DECL(AL_DISTORTION_EQCENTER), - DECL(AL_DISTORTION_EQBANDWIDTH), - - DECL(AL_ECHO_DELAY), - DECL(AL_ECHO_LRDELAY), - DECL(AL_ECHO_DAMPING), - DECL(AL_ECHO_FEEDBACK), - DECL(AL_ECHO_SPREAD), - - DECL(AL_FLANGER_WAVEFORM), - DECL(AL_FLANGER_PHASE), - DECL(AL_FLANGER_RATE), - DECL(AL_FLANGER_DEPTH), - DECL(AL_FLANGER_FEEDBACK), - DECL(AL_FLANGER_DELAY), - - DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), - DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), - DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), - - DECL(AL_RING_MODULATOR_FREQUENCY), - DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), - DECL(AL_RING_MODULATOR_WAVEFORM), - - DECL(AL_PITCH_SHIFTER_COARSE_TUNE), - DECL(AL_PITCH_SHIFTER_FINE_TUNE), - - DECL(AL_COMPRESSOR_ONOFF), - - DECL(AL_EQUALIZER_LOW_GAIN), - DECL(AL_EQUALIZER_LOW_CUTOFF), - DECL(AL_EQUALIZER_MID1_GAIN), - DECL(AL_EQUALIZER_MID1_CENTER), - DECL(AL_EQUALIZER_MID1_WIDTH), - DECL(AL_EQUALIZER_MID2_GAIN), - DECL(AL_EQUALIZER_MID2_CENTER), - DECL(AL_EQUALIZER_MID2_WIDTH), - DECL(AL_EQUALIZER_HIGH_GAIN), - DECL(AL_EQUALIZER_HIGH_CUTOFF), - - DECL(AL_DEDICATED_GAIN), - - DECL(AL_AUTOWAH_ATTACK_TIME), - DECL(AL_AUTOWAH_RELEASE_TIME), - DECL(AL_AUTOWAH_RESONANCE), - DECL(AL_AUTOWAH_PEAK_GAIN), - - DECL(AL_NUM_RESAMPLERS_SOFT), - DECL(AL_DEFAULT_RESAMPLER_SOFT), - DECL(AL_SOURCE_RESAMPLER_SOFT), - DECL(AL_RESAMPLER_NAME_SOFT), - - DECL(AL_SOURCE_SPATIALIZE_SOFT), - DECL(AL_AUTO_SOFT), - - DECL(AL_MAP_READ_BIT_SOFT), - DECL(AL_MAP_WRITE_BIT_SOFT), - DECL(AL_MAP_PERSISTENT_BIT_SOFT), - DECL(AL_PRESERVE_DATA_BIT_SOFT), - - DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), - 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), -}; -#undef DECL - -constexpr ALCchar alcNoError[] = "No Error"; -constexpr ALCchar alcErrInvalidDevice[] = "Invalid Device"; -constexpr ALCchar alcErrInvalidContext[] = "Invalid Context"; -constexpr ALCchar alcErrInvalidEnum[] = "Invalid Enum"; -constexpr ALCchar alcErrInvalidValue[] = "Invalid Value"; -constexpr ALCchar alcErrOutOfMemory[] = "Out of Memory"; - - -/************************************************ - * Global variables - ************************************************/ - -/* Enumerated device names */ -constexpr ALCchar alcDefaultName[] = "OpenAL Soft\0"; - -std::string alcAllDevicesList; -std::string alcCaptureDeviceList; - -/* Default is always the first in the list */ -std::string alcDefaultAllDevicesSpecifier; -std::string alcCaptureDefaultDeviceSpecifier; - -/* Default context extensions */ -constexpr ALchar alExtList[] = - "AL_EXT_ALAW " - "AL_EXT_BFORMAT " - "AL_EXT_DOUBLE " - "AL_EXT_EXPONENT_DISTANCE " - "AL_EXT_FLOAT32 " - "AL_EXT_IMA4 " - "AL_EXT_LINEAR_DISTANCE " - "AL_EXT_MCFORMATS " - "AL_EXT_MULAW " - "AL_EXT_MULAW_BFORMAT " - "AL_EXT_MULAW_MCFORMATS " - "AL_EXT_OFFSET " - "AL_EXT_source_distance_model " - "AL_EXT_SOURCE_RADIUS " - "AL_EXT_STEREO_ANGLES " - "AL_LOKI_quadriphonic " - "AL_SOFT_block_alignment " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFTX_effect_chain " - "AL_SOFTX_events " - "AL_SOFTX_filter_gain_ex " - "AL_SOFT_gain_clamp_ex " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize"; - -std::atomic<ALCenum> LastNullDeviceError{ALC_NO_ERROR}; - -/* Thread-local current context */ -void ReleaseThreadCtx(ALCcontext *context) -{ - auto ref = DecrementRef(&context->ref); - TRACEREF("ALCcontext %p decreasing refcount to %u\n", context, ref); - ERR("Context %p current for thread being destroyed, possible leak!\n", context); -} - -std::atomic<void(*)(ALCcontext*)> ThreadCtxProc{ReleaseThreadCtx}; -class ThreadCtx { - ALCcontext *ctx{nullptr}; - -public: - ~ThreadCtx() - { - auto destruct = ThreadCtxProc.load(); - if(destruct && ctx) - destruct(ctx); - ctx = nullptr; - } - - ALCcontext *get() const noexcept { return ctx; } - void set(ALCcontext *ctx_) noexcept { ctx = ctx_; } -}; -thread_local ThreadCtx LocalContext; -/* Process-wide current context */ -std::atomic<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}; - - -/************************************************ - * ALC information - ************************************************/ -constexpr ALCchar alcNoDeviceExtList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_thread_local_context " - "ALC_SOFT_loopback"; -constexpr ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_DEDICATED " - "ALC_EXT_disconnect " - "ALC_EXT_EFX " - "ALC_EXT_thread_local_context " - "ALC_SOFT_device_clock " - "ALC_SOFT_HRTF " - "ALC_SOFT_loopback " - "ALC_SOFT_output_limiter " - "ALC_SOFT_pause_device"; -constexpr ALCint alcMajorVersion = 1; -constexpr ALCint 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. - */ -al::FlexArray<ALCcontext*> EmptyContextArray{0u}; - - -void ALCdevice_IncRef(ALCdevice *device) -{ - auto ref = IncrementRef(&device->ref); - TRACEREF("ALCdevice %p increasing refcount to %u\n", device, ref); -} - -void ALCdevice_DecRef(ALCdevice *device) -{ - auto ref = DecrementRef(&device->ref); - TRACEREF("ALCdevice %p decreasing refcount to %u\n", device, ref); - if(UNLIKELY(ref == 0)) delete device; -} - -/* Simple RAII device reference. Takes the reference of the provided ALCdevice, - * and decrements it when leaving scope. Movable (transfer reference) but not - * copyable (no new references). - */ -class DeviceRef { - ALCdevice *mDev{nullptr}; - - void reset() noexcept - { - if(mDev) - ALCdevice_DecRef(mDev); - mDev = nullptr; - } - -public: - DeviceRef() noexcept = default; - DeviceRef(DeviceRef&& rhs) noexcept : mDev{rhs.mDev} - { rhs.mDev = nullptr; } - explicit DeviceRef(ALCdevice *dev) noexcept : mDev(dev) { } - ~DeviceRef() { reset(); } - - DeviceRef& operator=(const DeviceRef&) = delete; - DeviceRef& operator=(DeviceRef&& rhs) noexcept - { - std::swap(mDev, rhs.mDev); - return *this; - } - - operator bool() const noexcept { return mDev != nullptr; } - - ALCdevice* operator->() const noexcept { return mDev; } - ALCdevice* get() const noexcept { return mDev; } - - ALCdevice* release() noexcept - { - ALCdevice *ret{mDev}; - mDev = nullptr; - return ret; - } -}; - -inline bool operator==(const DeviceRef &lhs, const ALCdevice *rhs) noexcept -{ return lhs.get() == rhs; } -inline bool operator!=(const DeviceRef &lhs, const ALCdevice *rhs) noexcept -{ return !(lhs == rhs); } -inline bool operator<(const DeviceRef &lhs, const ALCdevice *rhs) noexcept -{ return lhs.get() < rhs; } - - -/************************************************ - * Device lists - ************************************************/ -al::vector<DeviceRef> DeviceList; -al::vector<ContextRef> ContextList; - -std::recursive_mutex ListLock; - - -void alc_initconfig(void) -{ - const char *str{getenv("ALSOFT_LOGLEVEL")}; - if(str) - { - long lvl = strtol(str, nullptr, 0); - if(lvl >= NoLog && lvl <= LogRef) - gLogLevel = static_cast<LogLevel>(lvl); - } - - str = getenv("ALSOFT_LOGFILE"); - if(str && str[0]) - { -#ifdef _WIN32 - std::wstring wname{utf8_to_wstr(str)}; - FILE *logfile = _wfopen(wname.c_str(), L"wt"); -#else - FILE *logfile = fopen(str, "wt"); -#endif - if(logfile) gLogFile = logfile; - else ERR("Failed to open log file '%s'\n", str); - } - - TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, - ALSOFT_GIT_BRANCH); - { - std::string names; - if(std::begin(BackendList) == BackendListEnd) - names += "(none)"; - else - { - const al::span<const BackendInfo> infos{std::begin(BackendList), BackendListEnd}; - names += infos[0].name; - for(const auto &backend : infos.subspan(1)) - { - names += ", "; - names += backend.name; - } - } - TRACE("Supported backends: %s\n", names.c_str()); - } - ReadALConfig(); - - str = getenv("__ALSOFT_SUSPEND_CONTEXT"); - if(str && *str) - { - if(strcasecmp(str, "ignore") == 0) - { - SuspendDefers = false; - TRACE("Selected context suspend behavior, \"ignore\"\n"); - } - else - ERR("Unhandled context suspend behavior setting: \"%s\"\n", str); - } - - int capfilter{0}; -#if defined(HAVE_SSE4_1) - capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) - capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) - capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) - capfilter |= CPU_CAP_SSE; -#endif -#ifdef HAVE_NEON - capfilter |= CPU_CAP_NEON; -#endif - if(auto cpuopt = ConfigValueStr(nullptr, nullptr, "disable-cpu-exts")) - { - str = cpuopt->c_str(); - if(strcasecmp(str, "all") == 0) - capfilter = 0; - else - { - const char *next = str; - do { - str = next; - while(isspace(str[0])) - str++; - next = strchr(str, ','); - - if(!str[0] || str[0] == ',') - continue; - - size_t len{next ? static_cast<size_t>(next-str) : strlen(str)}; - while(len > 0 && isspace(str[len-1])) - len--; - if(len == 3 && strncasecmp(str, "sse", len) == 0) - capfilter &= ~CPU_CAP_SSE; - else if(len == 4 && strncasecmp(str, "sse2", len) == 0) - capfilter &= ~CPU_CAP_SSE2; - else if(len == 4 && strncasecmp(str, "sse3", len) == 0) - capfilter &= ~CPU_CAP_SSE3; - else if(len == 6 && strncasecmp(str, "sse4.1", len) == 0) - capfilter &= ~CPU_CAP_SSE4_1; - else if(len == 4 && strncasecmp(str, "neon", len) == 0) - capfilter &= ~CPU_CAP_NEON; - else - WARN("Invalid CPU extension \"%s\"\n", str); - } while(next++); - } - } - FillCPUCaps(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 - - aluInit(); - aluInitMixer(); - - str = getenv("ALSOFT_TRAP_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - { - TrapALError = true; - TrapALCError = true; - } - else - { - str = getenv("ALSOFT_TRAP_AL_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - TrapALError = true; - TrapALError = !!GetConfigValueBool(nullptr, nullptr, "trap-al-error", TrapALError); - - str = getenv("ALSOFT_TRAP_ALC_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - TrapALCError = true; - TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", TrapALCError); - } - - if(auto boostopt = ConfigValueFloat(nullptr, "reverb", "boost")) - { - const float valf{std::isfinite(*boostopt) ? clampf(*boostopt, -24.0f, 24.0f) : 0.0f}; - ReverbBoost *= std::pow(10.0f, valf / 20.0f); - } - - auto devopt = ConfigValueStr(nullptr, nullptr, "drivers"); - if(const char *devs{getenv("ALSOFT_DRIVERS")}) - { - if(devs[0]) - devopt = devs; - } - if(devopt) - { - auto backendlist_cur = std::begin(BackendList); - - bool endlist{true}; - const char *next{devopt->c_str()}; - do { - const char *devs{next}; - while(isspace(devs[0])) - devs++; - next = strchr(devs, ','); - - const bool delitem{devs[0] == '-'}; - if(devs[0] == '-') devs++; - - if(!devs[0] || devs[0] == ',') - { - endlist = false; - continue; - } - endlist = true; - - size_t len{next ? (static_cast<size_t>(next-devs)) : strlen(devs)}; - while(len > 0 && isspace(devs[len-1])) --len; -#ifdef HAVE_WASAPI - /* HACK: For backwards compatibility, convert backend references of - * mmdevapi to wasapi. This should eventually be removed. - */ - if(len == 8 && strncmp(devs, "mmdevapi", len) == 0) - { - devs = "wasapi"; - len = 6; - } -#endif - - auto find_backend = [devs,len](const BackendInfo &backend) -> bool - { return len == strlen(backend.name) && strncmp(backend.name, devs, len) == 0; }; - auto this_backend = std::find_if(std::begin(BackendList), BackendListEnd, - find_backend); - - if(this_backend == BackendListEnd) - continue; - - if(delitem) - BackendListEnd = std::move(this_backend+1, BackendListEnd, this_backend); - else - backendlist_cur = std::rotate(backendlist_cur, this_backend, this_backend+1); - } while(next++); - - if(endlist) - BackendListEnd = backendlist_cur; - } - - auto init_backend = [](BackendInfo &backend) -> bool - { - if(PlaybackFactory && CaptureFactory) - return true; - - BackendFactory &factory = backend.getFactory(); - if(!factory.init()) - { - WARN("Failed to initialize backend \"%s\"\n", backend.name); - return true; - } - - TRACE("Initialized backend \"%s\"\n", backend.name); - if(!PlaybackFactory && factory.querySupport(BackendType::Playback)) - { - PlaybackFactory = &factory; - TRACE("Added \"%s\" for playback\n", backend.name); - } - if(!CaptureFactory && factory.querySupport(BackendType::Capture)) - { - CaptureFactory = &factory; - TRACE("Added \"%s\" for capture\n", backend.name); - } - return false; - }; - BackendListEnd = std::remove_if(std::begin(BackendList), BackendListEnd, init_backend); - - LoopbackBackendFactory::getFactory().init(); - - if(!PlaybackFactory) - WARN("No playback backend available!\n"); - if(!CaptureFactory) - WARN("No capture backend available!\n"); - - if(auto exclopt = ConfigValueStr(nullptr, nullptr, "excludefx")) - { - const char *next{exclopt->c_str()}; - do { - str = next; - next = strchr(str, ','); - - if(!str[0] || next == str) - continue; - - size_t len{next ? static_cast<size_t>(next-str) : strlen(str)}; - for(const EffectList &effectitem : gEffectList) - { - if(len == strlen(effectitem.name) && - strncmp(effectitem.name, str, len) == 0) - DisabledEffects[effectitem.type] = AL_TRUE; - } - } while(next++); - } - - InitEffect(&DefaultEffect); - auto defrevopt = ConfigValueStr(nullptr, nullptr, "default-reverb"); - if((str=getenv("ALSOFT_DEFAULT_REVERB")) && str[0]) - defrevopt = str; - if(defrevopt) LoadReverbPreset(defrevopt->c_str(), &DefaultEffect); -} -#define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) - - -/************************************************ - * Device enumeration - ************************************************/ -void ProbeAllDevicesList() -{ - DO_INITCONFIG(); - - std::lock_guard<std::recursive_mutex> _{ListLock}; - alcAllDevicesList.clear(); - if(PlaybackFactory) - PlaybackFactory->probe(DevProbe::Playback, &alcAllDevicesList); -} -void ProbeCaptureDeviceList() -{ - DO_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, - (WCHAR*)module, &module); - break; - - case DLL_PROCESS_DETACH: - 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) - { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - case DevFmtX51: return "5.1 Surround"; - case DevFmtX51Rear: return "5.1 Surround (Rear)"; - case DevFmtX61: return "6.1 Surround"; - case DevFmtX71: return "7.1 Surround"; - case DevFmtAmbi3D: return "Ambisonic 3D"; - } - return "(unknown channels)"; -} - -ALsizei BytesFromDevFmt(DevFmtType type) noexcept -{ - switch(type) - { - case DevFmtByte: return sizeof(ALbyte); - case DevFmtUByte: return sizeof(ALubyte); - case DevFmtShort: return sizeof(ALshort); - case DevFmtUShort: return sizeof(ALushort); - case DevFmtInt: return sizeof(ALint); - case DevFmtUInt: return sizeof(ALuint); - case DevFmtFloat: return sizeof(ALfloat); - } - return 0; -} -ALsizei ChannelsFromDevFmt(DevFmtChannels chans, ALsizei ambiorder) noexcept -{ - switch(chans) - { - 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) -{ - static const struct { - ALenum format; - DevFmtChannels channels; - DevFmtType type; - } list[] = { - { AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte }, - { AL_FORMAT_MONO16, DevFmtMono, DevFmtShort }, - { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat }, - - { AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte }, - { AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat }, - - { AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte }, - { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort }, - { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat }, - - { AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte }, - { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort }, - { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat }, - - { AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte }, - { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort }, - { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat }, - - { AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte }, - { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort }, - { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat }, - }; - - for(const auto &item : list) - { - if(item.format == format) - return al::make_optional(DevFmtPair{item.channels, item.type}); - } - - return al::nullopt; -} - -static ALCboolean IsValidALCType(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; - } - return ALC_FALSE; -} - -static ALCboolean IsValidALCChannels(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; - } - return ALC_FALSE; -} - -static ALCboolean IsValidAmbiLayout(ALCenum layout) -{ - switch(layout) - { - case ALC_ACN_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; - } - return ALC_FALSE; -} - -static ALCboolean IsValidAmbiScaling(ALCenum scaling) -{ - switch(scaling) - { - case ALC_N3D_SOFT: - case ALC_SN3D_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; - } - return ALC_FALSE; -} - -/************************************************ - * Miscellaneous ALC helpers - ************************************************/ - -/* SetDefaultWFXChannelOrder - * - * Sets the default channel order used by WaveFormatEx. - */ -void SetDefaultWFXChannelOrder(ALCdevice *device) -{ - device->RealOut.ChannelIndex.fill(-1); - - switch(device->FmtChans) - { - 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; - } -} - -/* SetDefaultChannelOrder - * - * Sets the default channel order used by most non-WaveFormatEx-based APIs. - */ -void SetDefaultChannelOrder(ALCdevice *device) -{ - device->RealOut.ChannelIndex.fill(-1); - - switch(device->FmtChans) - { - 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; - } -} - - -/* ALCcontext_DeferUpdates - * - * Defers/suspends updates for the given context's listener and sources. This - * does *NOT* stop mixing, but rather prevents certain property changes from - * taking effect. - */ -void ALCcontext_DeferUpdates(ALCcontext *context) -{ - context->DeferUpdates.store(true); -} - -/* ALCcontext_ProcessUpdates - * - * Resumes update processing after being deferred. - */ -void ALCcontext_ProcessUpdates(ALCcontext *context) -{ - std::lock_guard<std::mutex> _{context->PropLock}; - if(context->DeferUpdates.exchange(false)) - { - /* Tell the mixer to stop applying updates, then wait for any active - * updating to finish, before providing updates. - */ - context->HoldUpdates.store(true, std::memory_order_release); - while((context->UpdateCount.load(std::memory_order_acquire)&1) != 0) - std::this_thread::yield(); - - if(!context->PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateContextProps(context); - if(!context->Listener.PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateListenerProps(context); - UpdateAllEffectSlotProps(context); - UpdateAllSourceProps(context); - - /* Now with all updates declared, let the mixer continue applying them - * so they all happen at once. - */ - context->HoldUpdates.store(false, std::memory_order_release); - } -} - - -/* alcSetError - * - * Stores the latest ALC device error - */ -static void alcSetError(ALCdevice *device, ALCenum errorCode) -{ - WARN("Error generated on device %p, code 0x%04x\n", device, errorCode); - if(TrapALCError) - { -#ifdef _WIN32 - /* DebugBreak() will cause an exception if there is no debugger */ - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - - if(device) - device->LastError.store(errorCode); - else - LastNullDeviceError.store(errorCode); -} - - -static std::unique_ptr<Compressor> CreateDeviceLimiter(const ALCdevice *device, const ALfloat threshold) -{ - return CompressorInit(static_cast<ALuint>(device->RealOut.Buffer.size()), 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); -} - -/* 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) -{ - IncrementRef(&device->MixCount); - device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - device->SamplesDone = 0; - 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) -{ - HrtfRequestMode hrtf_userreq = Hrtf_Default; - HrtfRequestMode hrtf_appreq = Hrtf_Default; - ALCenum gainLimiter = device->LimiterState; - const ALsizei old_sends = device->NumAuxSends; - ALsizei new_sends = device->NumAuxSends; - DevFmtChannels oldChans; - DevFmtType oldType; - ALboolean update_failed; - ALCsizei hrtf_id = -1; - ALCuint oldFreq; - - if((!attrList || !attrList[0]) && device->Type == Loopback) - { - WARN("Missing attributes for loopback device\n"); - return ALC_INVALID_VALUE; - } - - // Check for attributes - if(attrList && attrList[0]) - { - ALCenum alayout{AL_NONE}; - ALCenum ascale{AL_NONE}; - ALCenum schans{AL_NONE}; - ALCenum stype{AL_NONE}; - ALCsizei attrIdx{0}; - ALCsizei aorder{0}; - ALCuint freq{0u}; - - ALuint numMono{device->NumMonoSources}; - ALuint numStereo{device->NumStereoSources}; - ALsizei numSends{old_sends}; - -#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v) - while(attrList[attrIdx]) - { - switch(attrList[attrIdx]) - { - case ALC_FORMAT_CHANNELS_SOFT: - schans = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, schans); - break; - - case ALC_FORMAT_TYPE_SOFT: - stype = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, stype); - break; - - case ALC_FREQUENCY: - freq = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FREQUENCY, freq); - break; - - case ALC_AMBISONIC_LAYOUT_SOFT: - alayout = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, alayout); - break; - - case ALC_AMBISONIC_SCALING_SOFT: - ascale = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, ascale); - break; - - case ALC_AMBISONIC_ORDER_SOFT: - aorder = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder); - break; - - case ALC_MONO_SOURCES: - numMono = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_MONO_SOURCES, numMono); - if(numMono > INT_MAX) numMono = 0; - break; - - case ALC_STEREO_SOURCES: - numStereo = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_STEREO_SOURCES, numStereo); - if(numStereo > INT_MAX) numStereo = 0; - break; - - case ALC_MAX_AUXILIARY_SENDS: - numSends = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends); - numSends = clampi(numSends, 0, MAX_SENDS); - break; - - case ALC_HRTF_SOFT: - TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]); - if(attrList[attrIdx + 1] == ALC_FALSE) - hrtf_appreq = Hrtf_Disable; - else if(attrList[attrIdx + 1] == ALC_TRUE) - hrtf_appreq = Hrtf_Enable; - else - hrtf_appreq = Hrtf_Default; - break; - - case 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); - break; - - default: - TRACE("0x%04X = %d (0x%x)\n", attrList[attrIdx], - attrList[attrIdx + 1], attrList[attrIdx + 1]); - break; - } - - attrIdx += 2; - } -#undef TRACE_ATTR - - const bool loopback{device->Type == Loopback}; - if(loopback) - { - if(!schans || !stype || !freq) - { - WARN("Missing format for loopback device\n"); - return ALC_INVALID_VALUE; - } - if(!IsValidALCChannels(schans) || !IsValidALCType(stype) || freq < MIN_OUTPUT_RATE) - return ALC_INVALID_VALUE; - if(schans == ALC_BFORMAT3D_SOFT) - { - if(!alayout || !ascale || !aorder) - { - WARN("Missing ambisonic info for loopback device\n"); - return ALC_INVALID_VALUE; - } - if(!IsValidAmbiLayout(alayout) || !IsValidAmbiScaling(ascale)) - 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) - 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 - { - freq = maxi(freq, MIN_OUTPUT_RATE); - - device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / - device->Frequency; - device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / - device->Frequency; - - device->Frequency = freq; - device->Flags.set<FrequencyRequest>(); - } - - 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); - } - else - { - device->Frequency = freq; - device->FmtChans = static_cast<DevFmtChannels>(schans); - device->FmtType = static_cast<DevFmtType>(stype); - if(schans == ALC_BFORMAT3D_SOFT) - { - device->mAmbiOrder = aorder; - device->mAmbiLayout = static_cast<AmbiLayout>(alayout); - device->mAmbiScale = static_cast<AmbiNorm>(ascale); - } - } - - if(numMono > INT_MAX-numStereo) - numMono = INT_MAX-numStereo; - numMono += numStereo; - if(auto srcsopt = ConfigValueUInt(devname, nullptr, "sources")) - { - if(*srcsopt <= 0) numMono = 256; - else numMono = *srcsopt; - } - else - numMono = maxu(numMono, 256); - numStereo = minu(numStereo, numMono); - numMono -= numStereo; - device->SourcesMax = numMono + numStereo; - - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; - - if(auto sendsopt = ConfigValueInt(devname, nullptr, "sends")) - new_sends = mini(numSends, clampi(*sendsopt, 0, MAX_SENDS)); - else - new_sends = numSends; - } - - if(device->Flags.get<DeviceRunning>()) - return ALC_NO_ERROR; - - device->AvgSpeakerDist = 0.0f; - device->Uhj_Encoder = nullptr; - device->AmbiDecoder = nullptr; - device->Bs2b = nullptr; - device->PostProcess = nullptr; - - device->Stablizer = nullptr; - device->Limiter = nullptr; - device->ChannelDelay.clear(); - - device->Dry.AmbiMap.fill(BFChannelConfig{}); - device->Dry.Buffer = {}; - std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u); - device->RealOut.ChannelIndex.fill(-1); - device->RealOut.Buffer = {}; - device->MixBuffer.clear(); - device->MixBuffer.shrink_to_fit(); - - UpdateClockBase(device); - device->FixedLatency = nanoseconds::zero(); - - device->DitherDepth = 0.0f; - device->DitherSeed = DITHER_RNG_SEED; - - /************************************************************************* - * Update device format request if HRTF is requested - */ - device->HrtfStatus = ALC_HRTF_DISABLED_SOFT; - if(device->Type != Loopback) - { - if(auto hrtfopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf")) - { - const char *hrtf{hrtfopt->c_str()}; - if(strcasecmp(hrtf, "true") == 0) - hrtf_userreq = Hrtf_Enable; - else if(strcasecmp(hrtf, "false") == 0) - hrtf_userreq = Hrtf_Disable; - else if(strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); - } - - if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable)) - { - HrtfEntry *hrtf{nullptr}; - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - if(!device->HrtfList.empty()) - { - if(hrtf_id >= 0 && static_cast<size_t>(hrtf_id) < device->HrtfList.size()) - hrtf = GetLoadedHrtf(device->HrtfList[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 - { - hrtf_userreq = Hrtf_Default; - hrtf_appreq = Hrtf_Disable; - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; - } - } - } - - 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->UpdateSize, device->BufferSize); - - try { - if(device->Backend->reset() == ALC_FALSE) - return ALC_INVALID_DEVICE; - } - catch(std::exception &e) { - ERR("Device reset failed: %s\n", e.what()); - return ALC_INVALID_DEVICE; - } - - if(device->FmtChans != oldChans && device->Flags.get<ChannelsRequest>()) - { - ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans), - DevFmtChannelsString(device->FmtChans)); - device->Flags.unset<ChannelsRequest>(); - } - if(device->FmtType != oldType && device->Flags.get<SampleTypeRequest>()) - { - ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType), - DevFmtTypeString(device->FmtType)); - device->Flags.unset<SampleTypeRequest>(); - } - if(device->Frequency != oldFreq && device->Flags.get<FrequencyRequest>()) - { - WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); - device->Flags.unset<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); - - 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... - */ - static constexpr size_t StablizerDelay{FrontStablizer::DelayLength}; - device->FixedLatency += nanoseconds{seconds{StablizerDelay}} / device->Frequency; - } - break; - case DevFmtMono: - case DevFmtStereo: - case DevFmtQuad: - case DevFmtAmbi3D: - break; - } - TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled"); - - if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1)) - { - ALint depth{ - ConfigValueInt(device->DeviceName.c_str(), 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; - } - } - - if(depth > 0) - { - depth = clampi(depth, 2, 24); - device->DitherDepth = std::pow(2.0f, static_cast<ALfloat>(depth-1)); - } - } - if(!(device->DitherDepth > 0.0f)) - TRACE("Dithering disabled\n"); - else - 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; - - /* 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(gainLimiter == ALC_DONT_CARE_SOFT) - { - 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; - } - } - if(gainLimiter == ALC_FALSE) - TRACE("Output limiter disabled\n"); - else - { - ALfloat 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; - } - 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; - device->Limiter = std::move(limiter); - TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB); - } - - TRACE("Fixed device latency: %ldns\n", (long)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()) - { - if(context->DefaultSlot) - { - ALeffectslot *slot = context->DefaultSlot.get(); - aluInitEffectPanning(slot, device); - - EffectState *state{slot->Effect.State}; - state->mOutTarget = device->Dry.Buffer; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); - } - - std::unique_lock<std::mutex> proplock{context->PropLock}; - std::unique_lock<std::mutex> slotlock{context->EffectSlotLock}; - for(auto &sublist : context->EffectSlotList) - { - uint64_t usemask = ~sublist.FreeMask; - while(usemask) - { - ALsizei idx = CTZ64(usemask); - ALeffectslot *slot = sublist.EffectSlots + idx; - - usemask &= ~(1_u64 << idx); - - aluInitEffectPanning(slot, device); - - EffectState *state{slot->Effect.State}; - state->mOutTarget = device->Dry.Buffer; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); - } - } - slotlock.unlock(); - - std::unique_lock<std::mutex> srclock{context->SourceLock}; - for(auto &sublist : context->SourceList) - { - uint64_t usemask = ~sublist.FreeMask; - while(usemask) - { - ALsizei idx = CTZ64(usemask); - ALsource *source = sublist.Sources + idx; - - usemask &= ~(1_u64 << idx); - - if(old_sends != device->NumAuxSends) - { - ALsizei s; - for(s = device->NumAuxSends;s < old_sends;s++) - { - if(source->Send[s].Slot) - DecrementRef(&source->Send[s].Slot->ref); - source->Send[s].Slot = nullptr; - } - source->Send.resize(device->NumAuxSends); - source->Send.shrink_to_fit(); - for(s = old_sends;s < device->NumAuxSends;s++) - { - source->Send[s].Slot = nullptr; - source->Send[s].Gain = 1.0f; - source->Send[s].GainHF = 1.0f; - source->Send[s].HFReference = LOWPASSFREQREF; - source->Send[s].GainLF = 1.0f; - source->Send[s].LFReference = HIGHPASSFREQREF; - } - } - - source->PropsClean.clear(std::memory_order_release); - } - } - - /* Clear any pre-existing voice property structs, in case the number of - * auxiliary sends is changing. Active sources will have updates - * respecified in UpdateAllSourceProps. - */ - ALvoiceProps *vprops{context->FreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)}; - while(vprops) - { - ALvoiceProps *next = vprops->next.load(std::memory_order_relaxed); - delete vprops; - vprops = next; - } - - auto voices = context->Voices.get(); - auto voices_end = voices->begin() + context->VoiceCount.load(std::memory_order_relaxed); - if(device->NumAuxSends < old_sends) - { - const ALsizei 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), - ALvoiceProps::SendData{}); - - std::fill(voice.mSend.begin()+num_sends, voice.mSend.end(), ALvoice::SendData{}); - 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(voices->begin(), voices_end, clear_sends); - } - std::for_each(voices->begin(), voices_end, - [device](ALvoice &voice) -> void - { - delete voice.mUpdate.exchange(nullptr, std::memory_order_acq_rel); - - /* Force the voice to stopped if it was stopping. */ - ALvoice::State vstate{ALvoice::Stopping}; - voice.mPlayState.compare_exchange_strong(vstate, ALvoice::Stopped, - std::memory_order_acquire, std::memory_order_acquire); - if(voice.mSourceID.load(std::memory_order_relaxed) == 0u) - return; - - if(device->AvgSpeakerDist > 0.0f) - { - /* Reinitialize the NFC filters for new parameters. */ - const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC / - (device->AvgSpeakerDist * 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); - } - } - ); - srclock.unlock(); - - context->PropsClean.test_and_set(std::memory_order_release); - UpdateContextProps(context); - context->Listener.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->Backend->start() == ALC_FALSE) - return ALC_INVALID_DEVICE; - device->Flags.set<DeviceRunning>(); - } - - 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", this); - - Backend = nullptr; - - size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, - [](size_t cur, const BufferSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - )}; - if(count > 0) - WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, - [](size_t cur, const EffectSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, - [](size_t cur, const FilterSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); - - if(mHrtf) - mHrtf->DecRef(); - mHrtf = nullptr; - - 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. - */ -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) - { - ALCdevice_IncRef(iter->get()); - return DeviceRef{iter->get()}; - } - return DeviceRef{}; -} - - -ALCcontext::ALCcontext(ALCdevice *device) : Device{device} -{ - PropsClean.test_and_set(std::memory_order_relaxed); -} - -/* InitContext - * - * Initializes context fields - */ -static ALvoid InitContext(ALCcontext *Context) -{ - ALlistener &listener = Context->Listener; - ALeffectslotArray *auxslots; - - //Validate Context - if(!Context->DefaultSlot) - auxslots = ALeffectslot::CreatePtrArray(0); - else - { - auxslots = ALeffectslot::CreatePtrArray(1); - (*auxslots)[0] = Context->DefaultSlot.get(); - } - Context->ActiveAuxSlots.store(auxslots, std::memory_order_relaxed); - - //Set globals - Context->mDistanceModel = DistanceModel::Default; - Context->SourceDistanceModel = AL_FALSE; - Context->DopplerFactor = 1.0f; - Context->DopplerVelocity = 1.0f; - Context->SpeedOfSound = SPEEDOFSOUNDMETRESPERSEC; - Context->MetersPerUnit = AL_DEFAULT_METERS_PER_UNIT; - - Context->ExtensionList = alExtList; - - - listener.Params.Matrix = alu::Matrix::Identity(); - listener.Params.Velocity = alu::Vector{}; - listener.Params.Gain = listener.Gain; - listener.Params.MetersPerUnit = Context->MetersPerUnit; - listener.Params.DopplerFactor = Context->DopplerFactor; - listener.Params.SpeedOfSound = Context->SpeedOfSound * Context->DopplerVelocity; - listener.Params.ReverbSpeedOfSound = listener.Params.SpeedOfSound * - listener.Params.MetersPerUnit; - listener.Params.SourceDistanceModel = Context->SourceDistanceModel; - listener.Params.mDistanceModel = Context->mDistanceModel; - - - Context->AsyncEvents = CreateRingBuffer(511, sizeof(AsyncEvent), false); - StartEventThrd(Context); -} - - -/* ALCcontext::~ALCcontext() - * - * Cleans up the context, and destroys any remaining objects the app failed to - * delete. Called once there's no more references on the context. - */ -ALCcontext::~ALCcontext() -{ - TRACE("Freeing context %p\n", this); - - ALcontextProps *cprops{Update.exchange(nullptr, std::memory_order_relaxed)}; - if(cprops) - { - TRACE("Freed unapplied context update %p\n", cprops); - al_free(cprops); - } - size_t count{0}; - cprops = FreeContextProps.exchange(nullptr, std::memory_order_acquire); - while(cprops) - { - ALcontextProps *next{cprops->next.load(std::memory_order_relaxed)}; - al_free(cprops); - cprops = next; - ++count; - } - TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); - - count = std::accumulate(SourceList.cbegin(), SourceList.cend(), size_t{0u}, - [](size_t cur, const SourceSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); - SourceList.clear(); - NumSources = 0; - - count = 0; - ALeffectslotProps *eprops{FreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; - while(eprops) - { - ALeffectslotProps *next{eprops->next.load(std::memory_order_relaxed)}; - if(eprops->State) eprops->State->DecRef(); - al_free(eprops); - eprops = next; - ++count; - } - TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); - - delete ActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed); - DefaultSlot = nullptr; - - count = std::accumulate(EffectSlotList.cbegin(), EffectSlotList.cend(), size_t{0u}, - [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); - EffectSlotList.clear(); - NumEffectSlots = 0; - - count = 0; - ALvoiceProps *vprops{FreeVoiceProps.exchange(nullptr, std::memory_order_acquire)}; - while(vprops) - { - 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"); - - Voices = nullptr; - VoiceCount.store(0, std::memory_order_relaxed); - - ALlistenerProps *lprops{Listener.Update.exchange(nullptr, std::memory_order_relaxed)}; - if(lprops) - { - TRACE("Freed unapplied listener update %p\n", lprops); - al_free(lprops); - } - count = 0; - lprops = FreeListenerProps.exchange(nullptr, std::memory_order_acquire); - while(lprops) - { - ALlistenerProps *next{lprops->next.load(std::memory_order_relaxed)}; - al_free(lprops); - lprops = next; - ++count; - } - TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s"); - - if(AsyncEvents) - { - count = 0; - auto evt_vec = AsyncEvents->getReadVector(); - if(evt_vec.first.len > 0) - { - 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"); - AsyncEvents->readAdvance(count); - } - - ALCdevice_DecRef(Device); -} - -/* ReleaseContext - * - * Removes the context reference from the given device and removes it from - * being current on the running thread or globally. Returns true if other - * contexts still exist on the device. - */ -static bool ReleaseContext(ALCcontext *context, ALCdevice *device) -{ - if(LocalContext.get() == context) - { - WARN("%p released while current on thread\n", context); - LocalContext.set(nullptr); - ALCcontext_DecRef(context); - } - - ALCcontext *origctx{context}; - if(GlobalContext.compare_exchange_strong(origctx, nullptr)) - ALCcontext_DecRef(context); - - bool ret{}; - { - using ContextArray = al::FlexArray<ALCcontext*>; - - /* First make sure this context exists in the device's list. */ - auto *oldarray = device->mContexts.load(std::memory_order_acquire); - if(auto toremove = std::count(oldarray->begin(), oldarray->end(), context)) - { - auto alloc_ctx_array = [](const size_t count) -> ContextArray* - { - if(count == 0) return &EmptyContextArray; - void *ptr{al_calloc(alignof(ContextArray), ContextArray::Sizeof(count))}; - return new (ptr) ContextArray{count}; - }; - 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, context)); - - /* Store the new context array in the device. Wait for any current - * mix to finish before deleting the old array. - */ - device->mContexts.store(newarray); - if(oldarray != &EmptyContextArray) - { - while((device->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - delete oldarray; - } - - ret = !newarray->empty(); - } - else - ret = !oldarray->empty(); - } - - StopEventThrd(context); - - return ret; -} - -static void ALCcontext_IncRef(ALCcontext *context) -{ - auto ref = IncrementRef(&context->ref); - TRACEREF("ALCcontext %p increasing refcount to %u\n", context, ref); -} - -void ALCcontext_DecRef(ALCcontext *context) -{ - auto ref = DecrementRef(&context->ref); - TRACEREF("ALCcontext %p decreasing refcount to %u\n", context, ref); - if(UNLIKELY(ref == 0)) delete context; -} - -/* VerifyContext - * - * Checks if the given context is valid, returning a new reference to it if so. - */ -static 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) - { - ALCcontext_IncRef(iter->get()); - return ContextRef{iter->get()}; - } - return ContextRef{}; -} - - -/* GetContextRef - * - * Returns a new reference to the currently active context for this thread. - */ -ContextRef GetContextRef(void) -{ - ALCcontext *context{LocalContext.get()}; - if(context) - ALCcontext_IncRef(context); - else - { - std::lock_guard<std::recursive_mutex> _{ListLock}; - context = GlobalContext.load(std::memory_order_acquire); - if(context) ALCcontext_IncRef(context); - } - return ContextRef{context}; -} - - -void AllocateVoices(ALCcontext *context, size_t num_voices) -{ - ALCdevice *device{context->Device}; - const ALsizei num_sends{device->NumAuxSends}; - - if(context->Voices && num_voices == context->Voices->size()) - return; - - std::unique_ptr<al::FlexArray<ALvoice>> voices; - { - void *ptr{al_calloc(16, al::FlexArray<ALvoice>::Sizeof(num_voices))}; - voices.reset(new (ptr) al::FlexArray<ALvoice>{num_voices}); - } - - const size_t v_count{minz(context->VoiceCount.load(std::memory_order_relaxed), num_voices)}; - if(context->Voices) - { - /* Copy the old voice data to the new storage. */ - auto viter = std::move(context->Voices->begin(), context->Voices->begin()+v_count, - voices->begin()); - - /* 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), - ALvoiceProps::SendData{}); - - std::fill(voice.mSend.begin()+num_sends, voice.mSend.end(), ALvoice::SendData{}); - 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(voices->begin(), viter, clear_sends); - } - - context->Voices = std::move(voices); - context->VoiceCount.store(static_cast<ALuint>(v_count), std::memory_order_relaxed); -} - - -/************************************************ - * 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 -{ - DeviceRef dev{VerifyDevice(device)}; - if(dev) return dev->LastError.exchange(ALC_NO_ERROR); - return LastNullDeviceError.exchange(ALC_NO_ERROR); -} -END_API_FUNC - - -/* alcSuspendContext - * - * Suspends updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *context) -START_API_FUNC -{ - if(!SuspendDefers) - return; - - ContextRef ctx{VerifyContext(context)}; - if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else - ALCcontext_DeferUpdates(ctx.get()); -} -END_API_FUNC - -/* alcProcessContext - * - * Resumes processing updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *context) -START_API_FUNC -{ - if(!SuspendDefers) - return; - - ContextRef ctx{VerifyContext(context)}; - if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else - ALCcontext_ProcessUpdates(ctx.get()); -} -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; - - switch(param) - { - case ALC_NO_ERROR: - value = alcNoError; - break; - - case ALC_INVALID_ENUM: - value = alcErrInvalidEnum; - break; - - case ALC_INVALID_VALUE: - value = alcErrInvalidValue; - break; - - case ALC_INVALID_DEVICE: - value = alcErrInvalidDevice; - break; - - case ALC_INVALID_CONTEXT: - value = alcErrInvalidContext; - break; - - case ALC_OUT_OF_MEMORY: - value = alcErrOutOfMemory; - break; - - case ALC_DEVICE_SPECIFIER: - value = alcDefaultName; - break; - - case ALC_ALL_DEVICES_SPECIFIER: - if(DeviceRef dev{VerifyDevice(Device)}) - value = dev->DeviceName.c_str(); - else - { - ProbeAllDevicesList(); - value = alcAllDevicesList.c_str(); - } - break; - - case ALC_CAPTURE_DEVICE_SPECIFIER: - if(DeviceRef dev{VerifyDevice(Device)}) - value = dev->DeviceName.c_str(); - else - { - ProbeCaptureDeviceList(); - value = alcCaptureDeviceList.c_str(); - } - break; - - /* Default devices are always first in the list */ - case ALC_DEFAULT_DEVICE_SPECIFIER: - value = alcDefaultName; - break; - - case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: - if(alcAllDevicesList.empty()) - ProbeAllDevicesList(); - - /* Copy first entry as default. */ - alcDefaultAllDevicesSpecifier = alcAllDevicesList.c_str(); - value = alcDefaultAllDevicesSpecifier.c_str(); - break; - - case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: - if(alcCaptureDeviceList.empty()) - ProbeCaptureDeviceList(); - - /* Copy first entry as default. */ - alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.c_str(); - value = alcCaptureDefaultDeviceSpecifier.c_str(); - break; - - case ALC_EXTENSIONS: - if(VerifyDevice(Device)) - value = alcExtensionList; - else - value = alcNoDeviceExtList; - break; - - case ALC_HRTF_SPECIFIER_SOFT: - if(DeviceRef dev{VerifyDevice(Device)}) - { - std::lock_guard<std::mutex> _{dev->StateLock}; - value = (dev->mHrtf ? dev->HrtfName.c_str() : ""); - } - else - alcSetError(nullptr, ALC_INVALID_DEVICE); - break; - - default: - alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM); - break; - } - - return value; -} -END_API_FUNC - - -static inline ALCsizei NumAttrsForDevice(ALCdevice *device) -{ - if(device->Type == Capture) return 9; - if(device->Type != Loopback) return 29; - if(device->FmtChans == DevFmtAmbi3D) - return 35; - return 29; -} - -static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCint> values) -{ - ALCsizei i; - - if(values.empty()) - { - alcSetError(device, ALC_INVALID_VALUE); - return 0; - } - - if(!device) - { - switch(param) - { - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_ATTRIBUTES_SIZE: - case ALC_ALL_ATTRIBUTES: - case ALC_FREQUENCY: - case ALC_REFRESH: - case ALC_SYNC: - case ALC_MONO_SOURCES: - case ALC_STEREO_SOURCES: - case ALC_CAPTURE_SAMPLES: - case ALC_FORMAT_CHANNELS_SOFT: - case ALC_FORMAT_TYPE_SOFT: - case ALC_AMBISONIC_LAYOUT_SOFT: - case ALC_AMBISONIC_SCALING_SOFT: - case ALC_AMBISONIC_ORDER_SOFT: - case ALC_MAX_AMBISONIC_ORDER_SOFT: - alcSetError(nullptr, ALC_INVALID_DEVICE); - return 0; - - default: - alcSetError(nullptr, ALC_INVALID_ENUM); - return 0; - } - return 0; - } - - if(device->Type == Capture) - { - switch(param) - { - case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); - return 1; - - case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < static_cast<size_t>(NumAttrsForDevice(device))) - 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; - values[i++] = alcMinorVersion; - values[i++] = ALC_CAPTURE_SAMPLES; - values[i++] = device->Backend->availableSamples(); - values[i++] = ALC_CONNECTED; - values[i++] = device->Connected.load(std::memory_order_relaxed); - values[i++] = 0; - } - return i; - - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_CAPTURE_SAMPLES: - { std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = device->Backend->availableSamples(); - } - return 1; - - case ALC_CONNECTED: - { std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } - return 1; - - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; - } - return 0; - } - - /* render device */ - switch(param) - { - case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); - return 1; - - case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < static_cast<size_t>(NumAttrsForDevice(device))) - 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; - values[i++] = alcMinorVersion; - values[i++] = ALC_EFX_MAJOR_VERSION; - values[i++] = alcEFXMajorVersion; - values[i++] = ALC_EFX_MINOR_VERSION; - values[i++] = alcEFXMinorVersion; - - values[i++] = ALC_FREQUENCY; - values[i++] = device->Frequency; - if(device->Type != Loopback) - { - values[i++] = ALC_REFRESH; - values[i++] = device->Frequency / device->UpdateSize; - - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; - } - else - { - if(device->FmtChans == DevFmtAmbi3D) - { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = static_cast<ALCint>(device->mAmbiLayout); - - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast<ALCint>(device->mAmbiScale); - - values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = device->mAmbiOrder; - } - - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = device->FmtChans; - - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = device->FmtType; - } - - values[i++] = ALC_MONO_SOURCES; - values[i++] = device->NumMonoSources; - - values[i++] = ALC_STEREO_SOURCES; - values[i++] = device->NumStereoSources; - - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = 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++] = 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++] = 0; - } - return i; - - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - - case ALC_MINOR_VERSION: - 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_FREQUENCY: - values[0] = device->Frequency; - return 1; - - case ALC_REFRESH: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - { std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = device->Frequency / device->UpdateSize; - } - return 1; - - case ALC_SYNC: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = ALC_FALSE; - return 1; - - case ALC_FORMAT_CHANNELS_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtChans; - return 1; - - case ALC_FORMAT_TYPE_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtType; - return 1; - - case ALC_AMBISONIC_LAYOUT_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = static_cast<ALCint>(device->mAmbiLayout); - return 1; - - case ALC_AMBISONIC_SCALING_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = static_cast<ALCint>(device->mAmbiScale); - return 1; - - case ALC_AMBISONIC_ORDER_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->mAmbiOrder; - return 1; - - case ALC_MONO_SOURCES: - values[0] = device->NumMonoSources; - return 1; - - case ALC_STEREO_SOURCES: - values[0] = device->NumStereoSources; - return 1; - - case ALC_MAX_AUXILIARY_SENDS: - values[0] = device->NumAuxSends; - return 1; - - case ALC_CONNECTED: - { std::lock_guard<std::mutex> _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } - return 1; - - case ALC_HRTF_SOFT: - values[0] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); - return 1; - - case ALC_HRTF_STATUS_SOFT: - values[0] = device->HrtfStatus; - return 1; - - case ALC_NUM_HRTF_SPECIFIERS_SOFT: - { std::lock_guard<std::mutex> _{device->StateLock}; - device->HrtfList.clear(); - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - values[0] = static_cast<ALCint>(minz(device->HrtfList.size(), - std::numeric_limits<ALCint>::max())); - } - return 1; - - case ALC_OUTPUT_LIMITER_SOFT: - values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE; - return 1; - - case ALC_MAX_AMBISONIC_ORDER_SOFT: - values[0] = MAX_AMBI_ORDER; - return 1; - - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; - } - 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 -{ - DeviceRef dev{VerifyDevice(device)}; - if(size <= 0 || values == nullptr) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - GetIntegerv(dev.get(), param, {values, values+size}); -} -END_API_FUNC - -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) -START_API_FUNC -{ - DeviceRef dev{VerifyDevice(device)}; - if(size <= 0 || values == nullptr) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else if(!dev || dev->Type == Capture) - { - auto ivals = al::vector<ALCint>(size); - size = GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()}); - std::copy(ivals.begin(), ivals.begin()+size, values); - } - else /* render device */ - { - switch(pname) - { - case ALC_ATTRIBUTES_SIZE: - *values = NumAttrsForDevice(dev.get())+4; - break; - - case ALC_ALL_ATTRIBUTES: - if(size < NumAttrsForDevice(dev.get())+4) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - ALsizei i{0}; - std::lock_guard<std::mutex> _{dev->StateLock}; - values[i++] = ALC_FREQUENCY; - values[i++] = dev->Frequency; - - if(dev->Type != Loopback) - { - values[i++] = ALC_REFRESH; - values[i++] = dev->Frequency / dev->UpdateSize; - - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; - } - else - { - if(dev->FmtChans == DevFmtAmbi3D) - { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = static_cast<ALCint64SOFT>(dev->mAmbiLayout); - - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast<ALCint64SOFT>(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; - values[i++] = dev->NumMonoSources; - - values[i++] = ALC_STEREO_SOURCES; - values[i++] = dev->NumStereoSources; - - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = dev->NumAuxSends; - - values[i++] = ALC_HRTF_SOFT; - values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); - - values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = dev->HrtfStatus; - - values[i++] = ALC_OUTPUT_LIMITER_SOFT; - values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; - - ClockLatency clock{GetClockLatency(dev.get())}; - values[i++] = ALC_DEVICE_CLOCK_SOFT; - values[i++] = clock.ClockTime.count(); - - values[i++] = ALC_DEVICE_LATENCY_SOFT; - values[i++] = clock.Latency.count(); - - values[i++] = 0; - } - break; - - case ALC_DEVICE_CLOCK_SOFT: - { std::lock_guard<std::mutex> _{dev->StateLock}; - nanoseconds basecount; - ALuint samplecount; - ALuint refcount; - do { - while(((refcount=ReadRef(&dev->MixCount))&1) != 0) - std::this_thread::yield(); - basecount = dev->ClockBase; - samplecount = dev->SamplesDone; - } while(refcount != ReadRef(&dev->MixCount)); - basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - *values = basecount.count(); - } - break; - - case ALC_DEVICE_LATENCY_SOFT: - { std::lock_guard<std::mutex> _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; - *values = clock.Latency.count(); - } - break; - - case ALC_DEVICE_CLOCK_LATENCY_SOFT: - if(size < 2) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - std::lock_guard<std::mutex> _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; - values[0] = clock.ClockTime.count(); - values[1] = clock.Latency.count(); - } - break; - - default: - auto ivals = al::vector<ALCint>(size); - size = GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()}); - std::copy(ivals.begin(), ivals.begin()+size, 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 -{ - DeviceRef dev{VerifyDevice(device)}; - if(!extName) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - size_t len = strlen(extName); - const char *ptr = (dev ? alcExtensionList : alcNoDeviceExtList); - while(ptr && *ptr) - { - if(strncasecmp(ptr, extName, len) == 0 && - (ptr[len] == '\0' || isspace(ptr[len]))) - return ALC_TRUE; - - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } - } - } - return ALC_FALSE; -} -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 -{ - if(!funcName) - { - DeviceRef dev{VerifyDevice(device)}; - alcSetError(dev.get(), ALC_INVALID_VALUE); - } - else - { - 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 -{ - if(!enumName) - { - DeviceRef dev{VerifyDevice(device)}; - alcSetError(dev.get(), ALC_INVALID_VALUE); - } - else - { - 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 -{ - /* Explicitly hold the list lock while taking the StateLock in case the - * device is asynchronously destroyed, to ensure this new context is - * properly cleaned up after being made. - */ - std::unique_lock<std::recursive_mutex> listlock{ListLock}; - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture || !dev->Connected.load(std::memory_order_relaxed)) - { - listlock.unlock(); - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return nullptr; - } - std::unique_lock<std::mutex> statelock{dev->StateLock}; - listlock.unlock(); - - dev->LastError.store(ALC_NO_ERROR); - - ContextRef context{new ALCcontext{dev.get()}}; - ALCdevice_IncRef(context->Device); - - ALCenum err{UpdateDeviceParams(dev.get(), attrList)}; - if(err != ALC_NO_ERROR) - { - alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device update failure"); - statelock.unlock(); - - return nullptr; - } - AllocateVoices(context.get(), 256); - - if(DefaultEffect.type != AL_EFFECT_NULL && dev->Type == Playback) - { - void *ptr{al_calloc(16, sizeof(ALeffectslot))}; - context->DefaultSlot = std::unique_ptr<ALeffectslot>{new (ptr) ALeffectslot{}}; - if(InitEffectSlot(context->DefaultSlot.get()) == AL_NO_ERROR) - aluInitEffectPanning(context->DefaultSlot.get(), dev.get()); - else - { - context->DefaultSlot = nullptr; - ERR("Failed to initialize the default effect slot\n"); - } - } - - InitContext(context.get()); - - if(auto volopt = ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust")) - { - const ALfloat 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)}; - if(db != valf) - WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); - context->GainBoost = std::pow(10.0f, db/20.0f); - TRACE("volume-adjust gain: %f\n", context->GainBoost); - } - } - UpdateListenerProps(context.get()); - - { - using ContextArray = al::FlexArray<ALCcontext*>; - - /* Allocate a new context array, which holds 1 more than the current/ - * old array. - */ - auto *oldarray = device->mContexts.load(); - const size_t newcount{oldarray->size()+1}; - void *ptr{al_calloc(alignof(ContextArray), ContextArray::Sizeof(newcount))}; - auto *newarray = new (ptr) ContextArray{newcount}; - - /* Copy the current/old context handles to the new array, appending the - * new context. - */ - auto iter = std::copy(oldarray->begin(), oldarray->end(), newarray->begin()); - *iter = context.get(); - - /* Store the new context array in the device. Wait for any current mix - * to finish before deleting the old array. - */ - dev->mContexts.store(newarray); - if(oldarray != &EmptyContextArray) - { - while((dev->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - delete oldarray; - } - } - statelock.unlock(); - - { - std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); - ALCcontext_IncRef(context.get()); - ContextList.insert(iter, ContextRef{context.get()}); - } - - if(context->DefaultSlot) - { - if(InitializeEffect(context.get(), context->DefaultSlot.get(), &DefaultEffect) == AL_NO_ERROR) - UpdateEffectSlotProps(context->DefaultSlot.get(), context.get()); - else - ERR("Failed to initialize the default effect\n"); - } - - TRACE("Created context %p\n", context.get()); - return context.get(); -} -END_API_FUNC - -/* alcDestroyContext - * - * Remove a context from its device - */ -ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context) -START_API_FUNC -{ - std::unique_lock<std::recursive_mutex> listlock{ListLock}; - auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); - if(iter == ContextList.end() || *iter != context) - { - listlock.unlock(); - alcSetError(nullptr, ALC_INVALID_CONTEXT); - return; - } - /* Hold an extra reference to this context so it remains valid until the - * ListLock is released. - */ - ContextRef ctx{std::move(*iter)}; - ContextList.erase(iter); - - ALCdevice *Device{ctx->Device}; - - std::lock_guard<std::mutex> _{Device->StateLock}; - if(!ReleaseContext(ctx.get(), Device) && Device->Flags.get<DeviceRunning>()) - { - Device->Backend->stop(); - Device->Flags.unset<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(); - return Context; -} -END_API_FUNC - -/* alcGetThreadContext - * - * Returns the currently active thread-local context - */ -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) -START_API_FUNC -{ return LocalContext.get(); } -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 -{ - /* context must be valid or nullptr */ - ContextRef ctx; - if(context) - { - ctx = VerifyContext(context); - if(!ctx) - { - alcSetError(nullptr, ALC_INVALID_CONTEXT); - return ALC_FALSE; - } - } - /* Release this reference (if any) to store it in the GlobalContext - * pointer. Take ownership of the reference (if any) that was previously - * stored there. - */ - ctx = ContextRef{GlobalContext.exchange(ctx.release())}; - - /* Reset (decrement) the previous global reference by replacing it with the - * thread-local context. Take ownership of the thread-local context - * reference (if any), clearing the storage to null. - */ - ctx = ContextRef{LocalContext.get()}; - if(ctx) LocalContext.set(nullptr); - /* 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 - */ -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) -START_API_FUNC -{ - /* context must be valid or nullptr */ - ContextRef ctx; - if(context) - { - ctx = VerifyContext(context); - if(!ctx) - { - alcSetError(nullptr, ALC_INVALID_CONTEXT); - return ALC_FALSE; - } - } - /* context's reference count is already incremented */ - ContextRef old{LocalContext.get()}; - LocalContext.set(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 -{ - ContextRef ctx{VerifyContext(Context)}; - if(!ctx) - { - alcSetError(nullptr, ALC_INVALID_CONTEXT); - return nullptr; - } - return ctx->Device; -} -END_API_FUNC - - -/* alcOpenDevice - * - * Opens the named device. - */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) -START_API_FUNC -{ - DO_INITCONFIG(); - - if(!PlaybackFactory) - { - alcSetError(nullptr, ALC_INVALID_VALUE); - return nullptr; - } - - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0 -#ifdef _WIN32 - /* Some old Windows apps hardcode these expecting OpenAL to use a - * specific audio API, even when they're not enumerated. Creative's - * router effectively ignores them too. - */ - || strcasecmp(deviceName, "DirectSound3D") == 0 || strcasecmp(deviceName, "DirectSound") == 0 - || strcasecmp(deviceName, "MMSYSTEM") == 0 -#endif - )) - deviceName = nullptr; - - DeviceRef device{new ALCdevice{Playback}}; - - /* Set output format */ - device->FmtChans = DevFmtChannelsDefault; - device->FmtType = DevFmtTypeDefault; - device->Frequency = DEFAULT_OUTPUT_RATE; - device->UpdateSize = DEFAULT_UPDATE_SIZE; - device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; - - device->SourcesMax = 256; - device->AuxiliaryEffectSlotMax = 64; - device->NumAuxSends = DEFAULT_SENDS; - - try { - /* Create the device backend. */ - device->Backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); - - /* Find a playback device to open */ - ALCenum err{device->Backend->open(deviceName)}; - if(err != ALC_NO_ERROR) - { - alcSetError(nullptr, err); - return nullptr; - } - } - catch(al::backend_exception &e) { - WARN("Failed to open playback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); - return nullptr; - } - - deviceName = device->DeviceName.c_str(); - if(auto chanopt = ConfigValueStr(deviceName, nullptr, "channels")) - { - static constexpr struct ChannelMap { - const char name[16]; - DevFmtChannels chans; - ALsizei order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround51rear", DevFmtX51Rear, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, - }; - - const ALCchar *fmt{chanopt->c_str()}; - auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), - [fmt](const ChannelMap &entry) -> bool - { return strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(chanlist)) - ERR("Unsupported channels: %s\n", fmt); - else - { - device->FmtChans = iter->chans; - device->mAmbiOrder = iter->order; - device->Flags.set<ChannelsRequest>(); - } - } - if(auto typeopt = ConfigValueStr(deviceName, nullptr, "sample-type")) - { - static constexpr struct TypeMap { - const char name[16]; - DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, - }; - - const ALCchar *fmt{typeopt->c_str()}; - auto iter = std::find_if(std::begin(typelist), std::end(typelist), - [fmt](const TypeMap &entry) -> bool - { return strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(typelist)) - ERR("Unsupported sample-type: %s\n", fmt); - else - { - device->FmtType = iter->type; - device->Flags.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 = clampi(DEFAULT_SENDS, 0, 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(strcasecmp(fmt, "fuma") == 0) - { - if(device->mAmbiOrder > 3) - ERR("FuMa is incompatible with %d%s order ambisonics (up to third-order only)\n", - device->mAmbiOrder, - (((device->mAmbiOrder%100)/10) == 1) ? "th" : - ((device->mAmbiOrder%10) == 1) ? "st" : - ((device->mAmbiOrder%10) == 2) ? "nd" : - ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); - else - { - device->mAmbiLayout = AmbiLayout::FuMa; - device->mAmbiScale = AmbiNorm::FuMa; - } - } - else if(strcasecmp(fmt, "ambix") == 0 || strcasecmp(fmt, "acn+sn3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::SN3D; - } - else if(strcasecmp(fmt, "acn+n3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::N3D; - } - else - ERR("Unsupported ambi-format: %s\n", fmt); - } - - { - std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - ALCdevice_IncRef(device.get()); - DeviceList.insert(iter, DeviceRef{device.get()}); - } - - TRACE("Created device %p, \"%s\"\n", device.get(), device->DeviceName.c_str()); - return device.get(); -} -END_API_FUNC - -/* alcCloseDevice - * - * Closes the given device. - */ -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) -START_API_FUNC -{ - std::unique_lock<std::recursive_mutex> listlock{ListLock}; - auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); - if(iter == DeviceList.end() || *iter != device) - { - alcSetError(nullptr, ALC_INVALID_DEVICE); - return ALC_FALSE; - } - if((*iter)->Type == Capture) - { - alcSetError(iter->get(), 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)}; - DeviceList.erase(iter); - - std::unique_lock<std::mutex> statelock{dev->StateLock}; - al::vector<ContextRef> orphanctxs; - for(ALCcontext *ctx : *dev->mContexts.load()) - { - auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); - if(iter != ContextList.end() && *iter == ctx) - { - orphanctxs.emplace_back(std::move(*iter)); - ContextList.erase(iter); - } - } - listlock.unlock(); - - for(ContextRef &context : orphanctxs) - { - WARN("Releasing context %p\n", context.get()); - ReleaseContext(context.get(), dev.get()); - } - orphanctxs.clear(); - - if(dev->Flags.get<DeviceRunning>()) - dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - - return ALC_TRUE; -} -END_API_FUNC - - -/************************************************ - * ALC capture functions - ************************************************/ -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) -START_API_FUNC -{ - DO_INITCONFIG(); - - if(!CaptureFactory) - { - alcSetError(nullptr, ALC_INVALID_VALUE); - return nullptr; - } - - if(samples <= 0) - { - alcSetError(nullptr, ALC_INVALID_VALUE); - return nullptr; - } - - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0)) - deviceName = nullptr; - - DeviceRef device{new ALCdevice{Capture}}; - - auto decompfmt = DecomposeDevFormat(format); - if(!decompfmt) - { - alcSetError(nullptr, ALC_INVALID_ENUM); - return nullptr; - } - - device->Frequency = frequency; - device->FmtChans = decompfmt->chans; - device->FmtType = decompfmt->type; - device->Flags.set<FrequencyRequest, ChannelsRequest, SampleTypeRequest>(); - - device->UpdateSize = samples; - device->BufferSize = samples; - - try { - device->Backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); - - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", - DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); - ALCenum err{device->Backend->open(deviceName)}; - if(err != ALC_NO_ERROR) - { - alcSetError(nullptr, err); - return nullptr; - } - } - catch(al::backend_exception &e) { - WARN("Failed to open capture device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); - return nullptr; - } - - { - std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - ALCdevice_IncRef(device.get()); - DeviceList.insert(iter, DeviceRef{device.get()}); - } - - TRACE("Created device %p, \"%s\"\n", device.get(), device->DeviceName.c_str()); - return device.get(); -} -END_API_FUNC - -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) -START_API_FUNC -{ - std::unique_lock<std::recursive_mutex> listlock{ListLock}; - auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); - if(iter == DeviceList.end() || *iter != device) - { - alcSetError(nullptr, ALC_INVALID_DEVICE); - return ALC_FALSE; - } - if((*iter)->Type != Capture) - { - alcSetError(iter->get(), ALC_INVALID_DEVICE); - return ALC_FALSE; - } - - DeviceRef dev{std::move(*iter)}; - DeviceList.erase(iter); - listlock.unlock(); - - std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) - dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - - return ALC_TRUE; -} -END_API_FUNC - -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) -START_API_FUNC -{ - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) - { - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return; - } - - 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>()) - { - if(dev->Backend->start()) - dev->Flags.set<DeviceRunning>(); - else - { - aluHandleDisconnect(dev.get(), "Device start failure"); - alcSetError(dev.get(), ALC_INVALID_DEVICE); - } - } -} -END_API_FUNC - -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) -START_API_FUNC -{ - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) - alcSetError(dev.get(), ALC_INVALID_DEVICE); - else - { - std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) - dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - } -} -END_API_FUNC - -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -START_API_FUNC -{ - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) - { - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return; - } - - ALCenum err{ALC_INVALID_VALUE}; - { std::lock_guard<std::mutex> _{dev->StateLock}; - BackendBase *backend{dev->Backend.get()}; - if(samples >= 0 && backend->availableSamples() >= static_cast<ALCuint>(samples)) - err = backend->captureSamples(buffer, samples); - } - if(err != ALC_NO_ERROR) - alcSetError(dev.get(), err); -} -END_API_FUNC - - -/************************************************ - * ALC loopback functions - ************************************************/ - -/* alcLoopbackOpenDeviceSOFT - * - * Open a loopback device, for manual rendering. - */ -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) -START_API_FUNC -{ - DO_INITCONFIG(); - - /* Make sure the device name, if specified, is us. */ - if(deviceName && strcmp(deviceName, alcDefaultName) != 0) - { - alcSetError(nullptr, ALC_INVALID_VALUE); - return nullptr; - } - - DeviceRef device{new ALCdevice{Loopback}}; - - device->SourcesMax = 256; - device->AuxiliaryEffectSlotMax = 64; - device->NumAuxSends = DEFAULT_SENDS; - - //Set output format - device->BufferSize = 0; - device->UpdateSize = 0; - - device->Frequency = DEFAULT_OUTPUT_RATE; - 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 = clampi(DEFAULT_SENDS, 0, clampi(*sendsopt, 0, MAX_SENDS)); - - device->NumStereoSources = 1; - device->NumMonoSources = device->SourcesMax - device->NumStereoSources; - - try { - device->Backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), - BackendType::Playback); - - // Open the "backend" - device->Backend->open("Loopback"); - } - catch(al::backend_exception &e) { - WARN("Failed to open loopback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); - return nullptr; - } - - { - std::lock_guard<std::recursive_mutex> _{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - ALCdevice_IncRef(device.get()); - DeviceList.insert(iter, DeviceRef{device.get()}); - } - - TRACE("Created device %p\n", device.get()); - return device.get(); -} -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) - 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) - return ALC_TRUE; - } - - return ALC_FALSE; -} -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); - else if(samples < 0 || (samples > 0 && buffer == nullptr)) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - BackendLockGuard _{*device->Backend}; - aluMixData(dev.get(), buffer, samples); - } -} -END_API_FUNC - - -/************************************************ - * ALC DSP pause/resume functions - ************************************************/ - -/* alcDevicePauseSOFT - * - * 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) - alcSetError(dev.get(), ALC_INVALID_DEVICE); - else - { - std::lock_guard<std::mutex> _{dev->StateLock}; - if(dev->Flags.get<DeviceRunning>()) - dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - dev->Flags.set<DevicePaused>(); - } -} -END_API_FUNC - -/* alcDeviceResumeSOFT - * - * 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) - { - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return; - } - - std::lock_guard<std::mutex> _{dev->StateLock}; - if(!dev->Flags.get<DevicePaused>()) - return; - dev->Flags.unset<DevicePaused>(); - if(dev->mContexts.load()->empty()) - return; - - if(dev->Backend->start() == ALC_FALSE) - { - aluHandleDisconnect(dev.get(), "Device start failure"); - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return; - } - dev->Flags.set<DeviceRunning>(); -} -END_API_FUNC - - -/************************************************ - * ALC HRTF functions - ************************************************/ - -/* alcGetStringiSOFT - * - * 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) - 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[index].name.c_str(); - alcSetError(dev.get(), ALC_INVALID_VALUE); - break; - - default: - alcSetError(dev.get(), ALC_INVALID_ENUM); - break; - } - - return nullptr; -} -END_API_FUNC - -/* alcResetDeviceSOFT - * - * 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) - { - listlock.unlock(); - alcSetError(dev.get(), ALC_INVALID_DEVICE); - return ALC_FALSE; - } - std::lock_guard<std::mutex> _{dev->StateLock}; - listlock.unlock(); - - /* 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>()) - dev->Backend->stop(); - dev->Flags.unset<DeviceRunning>(); - device->Connected.store(true); - - ALCenum err{UpdateDeviceParams(dev.get(), attribs)}; - if(LIKELY(err == ALC_NO_ERROR)) return ALC_TRUE; - - alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device start failure"); - return ALC_FALSE; -} -END_API_FUNC diff --git a/Alc/alcmain.h b/Alc/alcmain.h deleted file mode 100644 index a22e0e81..00000000 --- a/Alc/alcmain.h +++ /dev/null @@ -1,534 +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 "hrtf.h" -#include "inprogext.h" -#include "vector.h" - -class BFormatDec; -struct ALbuffer; -struct ALeffect; -struct ALfilter; -struct BackendBase; -struct Compressor; -struct EffectState; -struct FrontStablizer; -struct Uhj2Encoder; -struct bs2b; - - -#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) -#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -#else -static const union { - ALuint u; - ALubyte b[sizeof(ALuint)]; -} EndianTest = { 1 }; -#define IS_LITTLE_ENDIAN (EndianTest.b[0] == 1) -#endif - - -#define MIN_OUTPUT_RATE 8000 -#define DEFAULT_OUTPUT_RATE 44100 -#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 - - -enum Channel { - FrontLeft = 0, - FrontRight, - FrontCenter, - LFE, - BackLeft, - BackRight, - BackCenter, - SideLeft, - SideRight, - - UpperFrontLeft, - UpperFrontRight, - UpperBackLeft, - UpperBackRight, - LowerFrontLeft, - LowerFrontRight, - LowerBackLeft, - LowerBackRight, - - Aux0, - Aux1, - Aux2, - Aux3, - Aux4, - Aux5, - Aux6, - Aux7, - Aux8, - Aux9, - Aux10, - Aux11, - Aux12, - Aux13, - Aux14, - Aux15, - - MaxChannels -}; - - -/* Device formats */ -enum DevFmtType : 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 = ALbyte; }; -template<> -struct DevFmtTypeTraits<DevFmtUByte> { using Type = ALubyte; }; -template<> -struct DevFmtTypeTraits<DevFmtShort> { using Type = ALshort; }; -template<> -struct DevFmtTypeTraits<DevFmtUShort> { using Type = ALushort; }; -template<> -struct DevFmtTypeTraits<DevFmtInt> { using Type = ALint; }; -template<> -struct DevFmtTypeTraits<DevFmtUInt> { using Type = ALuint; }; -template<> -struct DevFmtTypeTraits<DevFmtFloat> { using Type = ALfloat; }; - - -ALsizei BytesFromDevFmt(DevFmtType type) noexcept; -ALsizei ChannelsFromDevFmt(DevFmtChannels chans, ALsizei ambiorder) noexcept; -inline ALsizei FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALsizei ambiorder) noexcept -{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } - -enum class AmbiLayout { - FuMa = ALC_FUMA_SOFT, /* FuMa channel order */ - ACN = ALC_ACN_SOFT, /* ACN channel order */ - - Default = ACN -}; - -enum class AmbiNorm { - FuMa = ALC_FUMA_SOFT, /* FuMa normalization */ - SN3D = ALC_SN3D_SOFT, /* SN3D normalization */ - N3D = ALC_N3D_SOFT, /* N3D normalization */ - - Default = SN3D -}; - - -enum DeviceType { - Playback, - Capture, - Loopback -}; - - -enum RenderMode { - NormalRender, - StereoPair, - HrtfRender -}; - - -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - -/* Maximum delay in samples for speaker distance compensation. */ -#define MAX_DELAY_LENGTH 1024 - -class DistanceComp { -public: - struct DistData { - ALfloat Gain{1.0f}; - ALsizei Length{0}; /* Valid range is [0...MAX_DELAY_LENGTH). */ - ALfloat *Buffer{nullptr}; - }; - -private: - 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; - ALsizei Index; -}; - -/* Size for temporary storage of buffer data, in ALfloats. Larger values need - * more memory, while smaller values may need more iterations. The value needs - * to be a sensible size, however, as it constrains the max stepping value used - * for mixing, as well as the maximum number of samples per mixing iteration. - */ -#define BUFFERSIZE 1024 - -using FloatBufferLine = std::array<float,BUFFERSIZE>; - -/* Maximum number of samples to pad on either end of a buffer for resampling. - * Note that both the beginning and end need padding! - */ -#define MAX_RESAMPLE_PADDING 24 - - -struct MixParams { - /* Coefficient channel mapping for mixing to the buffer. */ - std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{}; - - al::span<FloatBufferLine> Buffer; -}; - -struct RealMixParams { - std::array<ALint,MaxChannels> ChannelIndex{}; - - al::span<FloatBufferLine> Buffer; -}; - -using POSTPROCESS = void(*)(ALCdevice *device, const ALsizei SamplesToDo); - -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 { - RefCount ref{1u}; - - std::atomic<bool> Connected{true}; - const DeviceType Type{}; - - ALuint Frequency{}; - ALuint UpdateSize{}; - ALuint BufferSize{}; - - DevFmtChannels FmtChans{}; - DevFmtType FmtType{}; - ALboolean IsHeadphones{AL_FALSE}; - ALsizei mAmbiOrder{0}; - /* For DevFmtAmbi* output only, specifies the channel order and - * normalization. - */ - AmbiLayout mAmbiLayout{AmbiLayout::Default}; - AmbiNorm mAmbiScale{AmbiNorm::Default}; - - ALCenum LimiterState{ALC_DONT_CARE_SOFT}; - - std::string DeviceName; - - // Device flags - 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{}; - ALsizei 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_RESAMPLE_PADDING*2]; - alignas(16) ALfloat ResampledData[BUFFERSIZE]; - alignas(16) ALfloat FilteredData[BUFFERSIZE]; - union { - alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH]; - alignas(16) ALfloat NfcSampleData[BUFFERSIZE]; - }; - alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH]; - - /* Mixing buffer used by the Dry mix and Real output. */ - al::vector<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; - - POSTPROCESS PostProcess{}; - - 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(); - - ALsizei bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } - ALsizei channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } - ALsizei frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } - - 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" - - -enum { - /* End event thread processing. */ - EventType_KillThread = 0, - - /* User event types. */ - EventType_SourceStateChange = 1<<0, - EventType_BufferCompleted = 1<<1, - EventType_Error = 1<<2, - EventType_Performance = 1<<3, - EventType_Deprecated = 1<<4, - EventType_Disconnected = 1<<5, - - /* Internal events. */ - EventType_ReleaseEffectState = 65536, -}; - -struct AsyncEvent { - unsigned int EnumType{0u}; - union { - char dummy; - struct { - ALuint id; - ALenum state; - } srcstate; - struct { - ALuint id; - ALsizei count; - } bufcomp; - struct { - ALenum type; - ALuint id; - ALuint param; - ALchar msg[1008]; - } user; - EffectState *mEffectState; - } u{}; - - AsyncEvent() noexcept = default; - constexpr AsyncEvent(unsigned int type) noexcept : EnumType{type} { } -}; - - -void AllocateVoices(ALCcontext *context, size_t num_voices); - - -extern ALint RTPrioLevel; -void SetRTPriority(void); - -void SetDefaultChannelOrder(ALCdevice *device); -void SetDefaultWFXChannelOrder(ALCdevice *device); - -const ALCchar *DevFmtTypeString(DevFmtType type) noexcept; -const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept; - -/** - * GetChannelIdxByName - * - * Returns the index for the given channel name (e.g. FrontCenter), or -1 if it - * doesn't exist. - */ -inline ALint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept -{ return real.ChannelIndex[chan]; } - - -void StartEventThrd(ALCcontext *ctx); -void StopEventThrd(ALCcontext *ctx); - - -al::vector<std::string> SearchDataFiles(const char *match, const char *subdir); - -#endif diff --git a/Alc/alconfig.cpp b/Alc/alconfig.cpp deleted file mode 100644 index b246a91d..00000000 --- a/Alc/alconfig.cpp +++ /dev/null @@ -1,545 +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 - */ - -#ifdef _WIN32 -#ifdef __MINGW32__ -#define _WIN32_IE 0x501 -#else -#define _WIN32_IE 0x400 -#endif -#endif - -#include "config.h" - -#include "alconfig.h" - -#include <cstdlib> -#include <cctype> -#include <cstring> -#ifdef _WIN32_IE -#include <windows.h> -#include <shlobj.h> -#endif -#ifdef __APPLE__ -#include <CoreFoundation/CoreFoundation.h> -#endif - -#include <vector> -#include <string> -#include <algorithm> - -#include "alcmain.h" -#include "logging.h" -#include "compat.h" - - -namespace { - -struct ConfigEntry { - std::string key; - std::string value; -}; -al::vector<ConfigEntry> ConfOpts; - - -std::string &lstrip(std::string &line) -{ - size_t pos{0}; - while(pos < line.length() && std::isspace(line[pos])) - ++pos; - line.erase(0, pos); - return line; -} - -bool readline(std::istream &f, std::string &output) -{ - while(f.good() && f.peek() == '\n') - f.ignore(); - - return std::getline(f, output) && !output.empty(); -} - -std:: string expdup(const char *str) -{ - std::string output; - - while(*str != '\0') - { - const char *addstr; - size_t addstrlen; - - if(str[0] != '$') - { - const char *next = std::strchr(str, '$'); - addstr = str; - addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str); - - str += addstrlen; - } - else - { - str++; - if(*str == '$') - { - const char *next = std::strchr(str+1, '$'); - addstr = str; - addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str); - - str += addstrlen; - } - else - { - bool hasbraces{(*str == '{')}; - if(hasbraces) str++; - - std::string envname; - while((std::isalnum(*str) || *str == '_')) - envname += *(str++); - - if(hasbraces && *str != '}') - continue; - - if(hasbraces) str++; - if((addstr=std::getenv(envname.c_str())) == nullptr) - continue; - addstrlen = std::strlen(addstr); - } - } - if(addstrlen == 0) - continue; - - output.append(addstr, addstrlen); - } - - return output; -} - -void LoadConfigFromFile(std::istream &f) -{ - std::string curSection; - std::string buffer; - - while(readline(f, buffer)) - { - while(!buffer.empty() && std::isspace(buffer.back())) - buffer.pop_back(); - if(lstrip(buffer).empty()) - continue; - - buffer.push_back(0); - char *line{&buffer[0]}; - - if(line[0] == '[') - { - char *section = line+1; - char *endsection; - - endsection = std::strchr(section, ']'); - if(!endsection || section == endsection) - { - ERR("config parse error: bad line \"%s\"\n", line); - continue; - } - if(endsection[1] != 0) - { - char *end = endsection+1; - while(std::isspace(*end)) - ++end; - if(*end != 0 && *end != '#') - { - ERR("config parse error: bad line \"%s\"\n", line); - continue; - } - } - *endsection = 0; - - curSection.clear(); - if(strcasecmp(section, "general") != 0) - { - do { - char *nextp = std::strchr(section, '%'); - if(!nextp) - { - curSection += section; - break; - } - - curSection.append(section, nextp); - section = nextp; - - if(((section[1] >= '0' && section[1] <= '9') || - (section[1] >= 'a' && section[1] <= 'f') || - (section[1] >= 'A' && section[1] <= 'F')) && - ((section[2] >= '0' && section[2] <= '9') || - (section[2] >= 'a' && section[2] <= 'f') || - (section[2] >= 'A' && section[2] <= 'F'))) - { - unsigned char b = 0; - if(section[1] >= '0' && section[1] <= '9') - b = (section[1]-'0') << 4; - else if(section[1] >= 'a' && section[1] <= 'f') - b = (section[1]-'a'+0xa) << 4; - else if(section[1] >= 'A' && section[1] <= 'F') - b = (section[1]-'A'+0x0a) << 4; - if(section[2] >= '0' && section[2] <= '9') - b |= (section[2]-'0'); - else if(section[2] >= 'a' && section[2] <= 'f') - b |= (section[2]-'a'+0xa); - else if(section[2] >= 'A' && section[2] <= 'F') - b |= (section[2]-'A'+0x0a); - curSection += static_cast<char>(b); - section += 3; - } - else if(section[1] == '%') - { - curSection += '%'; - section += 2; - } - else - { - curSection += '%'; - section += 1; - } - } while(*section != 0); - } - - continue; - } - - char *comment{std::strchr(line, '#')}; - if(comment) *(comment++) = 0; - if(!line[0]) continue; - - char key[256]{}; - char value[256]{}; - if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 || - std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 || - std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2) - { - /* sscanf doesn't handle '' or "" as empty values, so clip it - * manually. */ - if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0) - value[0] = 0; - } - else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2) - { - /* Special case for 'key =' */ - value[0] = 0; - } - else - { - ERR("config parse error: malformed option line: \"%s\"\n\n", line); - continue; - } - - std::string fullKey; - if(!curSection.empty()) - { - fullKey += curSection; - fullKey += '/'; - } - fullKey += key; - while(!fullKey.empty() && std::isspace(fullKey.back())) - fullKey.pop_back(); - - /* 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; } - ); - if(ent != ConfOpts.end()) - ent->value = expdup(value); - else - { - ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)}); - ent = ConfOpts.end()-1; - } - - TRACE("found '%s' = '%s'\n", ent->key.c_str(), ent->value.c_str()); - } - ConfOpts.shrink_to_fit(); -} - -} // namespace - - -#ifdef _WIN32 -void ReadALConfig() -{ - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) - { - std::string filepath{wstr_to_utf8(buffer)}; - filepath += "\\alsoft.ini"; - - TRACE("Loading config %s...\n", filepath.c_str()); - al::ifstream f{filepath}; - if(f.is_open()) - LoadConfigFromFile(f); - } - - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) - { - ppath += "\\alsoft.ini"; - TRACE("Loading config %s...\n", ppath.c_str()); - al::ifstream f{ppath}; - if(f.is_open()) - LoadConfigFromFile(f); - } - - const WCHAR *str{_wgetenv(L"ALSOFT_CONF")}; - if(str != nullptr && *str) - { - std::string filepath{wstr_to_utf8(str)}; - - TRACE("Loading config %s...\n", filepath.c_str()); - al::ifstream f{filepath}; - if(f.is_open()) - LoadConfigFromFile(f); - } -} -#else -void ReadALConfig() -{ - const char *str{"/etc/openal/alsoft.conf"}; - - TRACE("Loading config %s...\n", str); - al::ifstream f{str}; - if(f.is_open()) - LoadConfigFromFile(f); - f.close(); - - if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0) - str = "/etc/xdg"; - std::string confpaths = str; - /* Go through the list in reverse, since "the order of base directories - * denotes their importance; the first directory listed is the most - * important". Ergo, we need to load the settings from the later dirs - * first so that the settings in the earlier dirs override them. - */ - std::string fname; - while(!confpaths.empty()) - { - auto next = confpaths.find_last_of(':'); - if(next < confpaths.length()) - { - fname = confpaths.substr(next+1); - confpaths.erase(next); - } - else - { - fname = confpaths; - confpaths.clear(); - } - - if(fname.empty() || fname.front() != '/') - WARN("Ignoring XDG config dir: %s\n", fname.c_str()); - else - { - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; - - TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; - if(f.is_open()) - LoadConfigFromFile(f); - } - fname.clear(); - } - -#ifdef __APPLE__ - CFBundleRef mainBundle = CFBundleGetMainBundle(); - if(mainBundle) - { - unsigned char fileName[PATH_MAX]; - CFURLRef configURL; - - if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && - CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) - { - al::ifstream f{reinterpret_cast<char*>(fileName)}; - if(f.is_open()) - LoadConfigFromFile(f); - } - } -#endif - - if((str=getenv("HOME")) != nullptr && *str) - { - fname = str; - if(fname.back() != '/') fname += "/.alsoftrc"; - else fname += ".alsoftrc"; - - TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; - if(f.is_open()) - LoadConfigFromFile(f); - } - - if((str=getenv("XDG_CONFIG_HOME")) != nullptr && str[0] != 0) - { - fname = str; - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; - } - else - { - fname.clear(); - if((str=getenv("HOME")) != nullptr && str[0] != 0) - { - fname = str; - if(fname.back() != '/') fname += "/.config/alsoft.conf"; - else fname += ".config/alsoft.conf"; - } - } - if(!fname.empty()) - { - TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; - if(f.is_open()) - LoadConfigFromFile(f); - } - - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) - { - if(ppath.back() != '/') ppath += "/alsoft.conf"; - else ppath += "alsoft.conf"; - - TRACE("Loading config %s...\n", ppath.c_str()); - al::ifstream f{ppath}; - if(f.is_open()) - LoadConfigFromFile(f); - } - - if((str=getenv("ALSOFT_CONF")) != nullptr && *str) - { - TRACE("Loading config %s...\n", str); - al::ifstream f{str}; - if(f.is_open()) - LoadConfigFromFile(f); - } -} -#endif - -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def) -{ - if(!keyName) - return def; - - std::string key; - if(blockName && strcasecmp(blockName, "general") != 0) - { - key = blockName; - if(devName) - { - key += '/'; - key += devName; - } - key += '/'; - key += keyName; - } - else - { - if(devName) - { - key = devName; - key += '/'; - } - key += keyName; - } - - auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), - [&key](const ConfigEntry &entry) -> bool - { return entry.key == key; } - ); - if(iter != ConfOpts.cend()) - { - TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); - if(!iter->value.empty()) - return iter->value.c_str(); - return def; - } - - if(!devName) - { - TRACE("Key %s not found\n", key.c_str()); - return def; - } - return GetConfigValue(nullptr, blockName, keyName, def); -} - -int ConfigValueExists(const char *devName, const char *blockName, const char *keyName) -{ - 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); -} - -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))); -} - -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))); -} - -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)); -} - -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( - strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 || - strcasecmp(val, "on") == 0 || atoi(val) != 0); -} - -int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def) -{ - const char *val = GetConfigValue(devName, blockName, keyName, ""); - - if(!val[0]) return def != 0; - return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 || - strcasecmp(val, "on") == 0 || atoi(val) != 0); -} diff --git a/Alc/alconfig.h b/Alc/alconfig.h deleted file mode 100644 index ffc7adad..00000000 --- a/Alc/alconfig.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ALCONFIG_H -#define ALCONFIG_H - -#include <string> - -#include "aloptional.h" - -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); - -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); -al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName); -al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName); -al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName); - -#endif /* ALCONFIG_H */ diff --git a/Alc/alcontext.h b/Alc/alcontext.h deleted file mode 100644 index cf956079..00000000 --- a/Alc/alcontext.h +++ /dev/null @@ -1,217 +0,0 @@ -#ifndef ALCONTEXT_H -#define ALCONTEXT_H - -#include <mutex> -#include <atomic> -#include <memory> -#include <thread> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" -#include "inprogext.h" - -#include "atomic.h" -#include "vector.h" -#include "threads.h" -#include "almalloc.h" -#include "alnumeric.h" - -#include "alListener.h" -#include "alu.h" - - -struct ALsource; -struct ALeffectslot; -struct ALcontextProps; -struct ALlistenerProps; -struct ALvoiceProps; -struct ALeffectslotProps; -struct RingBuffer; - -enum class DistanceModel { - InverseClamped = AL_INVERSE_DISTANCE_CLAMPED, - LinearClamped = AL_LINEAR_DISTANCE_CLAMPED, - ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED, - Inverse = AL_INVERSE_DISTANCE, - Linear = AL_LINEAR_DISTANCE, - Exponent = AL_EXPONENT_DISTANCE, - Disable = AL_NONE, - - Default = InverseClamped -}; - -struct SourceSubList { - uint64_t FreeMask{~0_u64}; - ALsource *Sources{nullptr}; /* 64 */ - - SourceSubList() noexcept = default; - SourceSubList(const SourceSubList&) = delete; - SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} - { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } - ~SourceSubList(); - - SourceSubList& operator=(const SourceSubList&) = delete; - SourceSubList& operator=(SourceSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } -}; - -struct EffectSlotSubList { - uint64_t FreeMask{~0_u64}; - ALeffectslot *EffectSlots{nullptr}; /* 64 */ - - EffectSlotSubList() noexcept = default; - EffectSlotSubList(const EffectSlotSubList&) = delete; - EffectSlotSubList(EffectSlotSubList&& rhs) noexcept - : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} - { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } - ~EffectSlotSubList(); - - EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; - EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } -}; - -struct ALCcontext { - RefCount ref{1u}; - - al::vector<SourceSubList> SourceList; - ALuint NumSources{0}; - std::mutex SourceLock; - - al::vector<EffectSlotSubList> EffectSlotList; - ALuint NumEffectSlots{0u}; - std::mutex EffectSlotLock; - - std::atomic<ALenum> LastError{AL_NO_ERROR}; - - DistanceModel mDistanceModel{DistanceModel::Default}; - ALboolean SourceDistanceModel{AL_FALSE}; - - ALfloat DopplerFactor{1.0f}; - ALfloat DopplerVelocity{1.0f}; - ALfloat SpeedOfSound{}; - ALfloat MetersPerUnit{1.0f}; - - std::atomic_flag PropsClean; - std::atomic<bool> DeferUpdates{false}; - - std::mutex PropLock; - - /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit - * indicates if updates are currently happening). - */ - RefCount UpdateCount{0u}; - std::atomic<bool> HoldUpdates{false}; - - ALfloat GainBoost{1.0f}; - - std::atomic<ALcontextProps*> Update{nullptr}; - - /* Linked lists of unused property containers, free to use for future - * updates. - */ - std::atomic<ALcontextProps*> FreeContextProps{nullptr}; - std::atomic<ALlistenerProps*> FreeListenerProps{nullptr}; - std::atomic<ALvoiceProps*> FreeVoiceProps{nullptr}; - std::atomic<ALeffectslotProps*> FreeEffectslotProps{nullptr}; - - std::unique_ptr<al::FlexArray<ALvoice>> Voices{nullptr}; - std::atomic<ALuint> VoiceCount{0u}; - - using ALeffectslotArray = al::FlexArray<ALeffectslot*>; - std::atomic<ALeffectslotArray*> ActiveAuxSlots{nullptr}; - - std::thread EventThread; - al::semaphore EventSem; - std::unique_ptr<RingBuffer> AsyncEvents; - std::atomic<ALbitfieldSOFT> EnabledEvts{0u}; - std::mutex EventCbLock; - ALEVENTPROCSOFT EventCb{}; - void *EventParam{nullptr}; - - /* Default effect slot */ - std::unique_ptr<ALeffectslot> DefaultSlot; - - ALCdevice *const Device; - const ALCchar *ExtensionList{nullptr}; - - ALlistener Listener{}; - - - ALCcontext(ALCdevice *device); - ALCcontext(const ALCcontext&) = delete; - ALCcontext& operator=(const ALCcontext&) = delete; - ~ALCcontext(); - - DEF_NEWDEL(ALCcontext) -}; - -void ALCcontext_DecRef(ALCcontext *context); - -void UpdateContextProps(ALCcontext *context); - -void ALCcontext_DeferUpdates(ALCcontext *context); -void ALCcontext_ProcessUpdates(ALCcontext *context); - - -/* Simple RAII context reference. Takes the reference of the provided - * ALCcontext, and decrements it when leaving scope. Movable (transfer - * reference) but not copyable (no new references). - */ -class ContextRef { - ALCcontext *mCtx{nullptr}; - - void reset() noexcept - { - if(mCtx) - ALCcontext_DecRef(mCtx); - mCtx = nullptr; - } - -public: - ContextRef() noexcept = default; - ContextRef(ContextRef&& rhs) noexcept : mCtx{rhs.mCtx} - { rhs.mCtx = nullptr; } - explicit ContextRef(ALCcontext *ctx) noexcept : mCtx(ctx) { } - ~ContextRef() { reset(); } - - ContextRef& operator=(const ContextRef&) = delete; - ContextRef& operator=(ContextRef&& rhs) noexcept - { std::swap(mCtx, rhs.mCtx); return *this; } - - operator bool() const noexcept { return mCtx != nullptr; } - - ALCcontext* operator->() const noexcept { return mCtx; } - ALCcontext* get() const noexcept { return mCtx; } - - ALCcontext* release() noexcept - { - ALCcontext *ret{mCtx}; - mCtx = nullptr; - return ret; - } -}; - -inline bool operator==(const ContextRef &lhs, const ALCcontext *rhs) noexcept -{ return lhs.get() == rhs; } -inline bool operator!=(const ContextRef &lhs, const ALCcontext *rhs) noexcept -{ return !(lhs == rhs); } -inline bool operator<(const ContextRef &lhs, const ALCcontext *rhs) noexcept -{ return lhs.get() < rhs; } - -ContextRef GetContextRef(void); - - -struct ALcontextProps { - ALfloat DopplerFactor; - ALfloat DopplerVelocity; - ALfloat SpeedOfSound; - ALboolean SourceDistanceModel; - DistanceModel mDistanceModel; - ALfloat MetersPerUnit; - - std::atomic<ALcontextProps*> next; -}; - -#endif /* ALCONTEXT_H */ diff --git a/Alc/alu.cpp b/Alc/alu.cpp deleted file mode 100644 index cc1a5a98..00000000 --- a/Alc/alu.cpp +++ /dev/null @@ -1,1798 +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 "alu.h" - -#include <algorithm> -#include <array> -#include <atomic> -#include <cassert> -#include <chrono> -#include <climits> -#include <cmath> -#include <cstdarg> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <functional> -#include <iterator> -#include <limits> -#include <memory> -#include <new> -#include <numeric> -#include <utility> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/efx.h" - -#include "alAuxEffectSlot.h" -#include "alBuffer.h" -#include "alcmain.h" -#include "alEffect.h" -#include "alListener.h" -#include "alcontext.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alspan.h" -#include "ambidefs.h" -#include "atomic.h" -#include "bformatdec.h" -#include "bs2b.h" -#include "cpu_caps.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 "opthelpers.h" -#include "ringbuffer.h" -#include "threads.h" -#include "uhjfilter.h" -#include "vecmat.h" -#include "vector.h" - -#include "bsinc_inc.h" - - -namespace { - -using namespace std::placeholders; - -ALfloat InitConeScale() -{ - ALfloat ret{1.0f}; - const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret *= 0.5f; - return ret; -} - -ALfloat InitZScale() -{ - ALfloat ret{1.0f}; - const char *str{getenv("__ALSOFT_REVERSE_Z")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret *= -1.0f; - return ret; -} - -ALboolean InitReverbSOS() -{ - ALboolean ret{AL_FALSE}; - const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret = AL_TRUE; - return ret; -} - -} // namespace - -/* Cone scalar */ -const ALfloat ConeScale{InitConeScale()}; - -/* Localized Z scalar for mono sources */ -const ALfloat ZScale{InitZScale()}; - -/* Force default speed of sound for distance-related reverb decay. */ -const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()}; - - -namespace { - -void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS]) -{ - std::fill(std::begin(f), std::end(f), 0.0f); -} - -struct ChanMap { - Channel channel; - ALfloat angle; - ALfloat elevation; -}; - -HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_<CTag>; -inline HrtfDirectMixerFunc SelectHrtfMixer(void) -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixDirectHrtf_<NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixDirectHrtf_<SSETag>; -#endif - - return MixDirectHrtf_<CTag>; -} - -} // namespace - -void aluInit(void) -{ - MixDirectHrtf = SelectHrtfMixer(); -} - - -void ProcessHrtf(ALCdevice *device, const ALsizei SamplesToDo) -{ - /* HRTF is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); - - DirectHrtfState *state{device->mHrtfState.get()}; - MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx], device->Dry.Buffer, - device->HrtfAccumData, state, SamplesToDo); -} - -void ProcessAmbiDec(ALCdevice *device, const ALsizei SamplesToDo) -{ - BFormatDec *ambidec{device->AmbiDecoder.get()}; - ambidec->process(device->RealOut.Buffer, device->Dry.Buffer.data(), SamplesToDo); -} - -void ProcessUhj(ALCdevice *device, const ALsizei SamplesToDo) -{ - /* UHJ is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); - - /* Encode to stereo-compatible 2-channel UHJ output. */ - Uhj2Encoder *uhj2enc{device->Uhj_Encoder.get()}; - uhj2enc->encode(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx], - device->Dry.Buffer.data(), SamplesToDo); -} - -void ProcessBs2b(ALCdevice *device, const ALsizei SamplesToDo) -{ - /* First, decode the ambisonic mix to the "real" output. */ - BFormatDec *ambidec{device->AmbiDecoder.get()}; - ambidec->process(device->RealOut.Buffer, device->Dry.Buffer.data(), SamplesToDo); - - /* BS2B is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); - - /* Now apply the BS2B binaural/crossfeed filter. */ - bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx].data(), - device->RealOut.Buffer[ridx].data(), SamplesToDo); -} - - -/* Prepares the interpolator for a given rate (determined by increment). - * - * With a bit of work, and a trade of memory for CPU cost, this could be - * modified for use with an interpolated increment for buttery-smooth pitch - * changes. - */ -void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table) -{ - ALsizei si{BSINC_SCALE_COUNT - 1}; - ALfloat sf{0.0f}; - - if(increment > FRACTIONONE) - { - sf = static_cast<ALfloat>FRACTIONONE / increment; - sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange); - si = float2int(sf); - /* The interpolation factor is fit to this diagonally-symmetric curve - * to reduce the transition ripple caused by interpolating different - * scales of the sinc function. - */ - sf = 1.0f - std::cos(std::asin(sf - si)); - } - - state->sf = sf; - state->m = table->m[si]; - state->l = (state->m/2) - 1; - state->filter = table->Tab + table->filterOffset[si]; -} - - -namespace { - -/* This RNG method was created based on the math found in opusdec. It's quick, - * and starting with a seed value of 22222, is suitable for generating - * whitenoise. - */ -inline ALuint dither_rng(ALuint *seed) noexcept -{ - *seed = (*seed * 96314165) + 907633515; - return *seed; -} - - -inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2) -{ - 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 - }; -} - -inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2) -{ - return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2]; -} - - -alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) 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] - }; -} - - -bool CalcContextParams(ALCcontext *Context) -{ - ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props) return false; - - ALlistener &Listener = Context->Listener; - Listener.Params.MetersPerUnit = props->MetersPerUnit; - - Listener.Params.DopplerFactor = props->DopplerFactor; - Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; - if(!OverrideReverbSpeedOfSound) - Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound * - Listener.Params.MetersPerUnit; - - Listener.Params.SourceDistanceModel = props->SourceDistanceModel; - Listener.Params.mDistanceModel = props->mDistanceModel; - - AtomicReplaceHead(Context->FreeContextProps, props); - return true; -} - -bool CalcListenerParams(ALCcontext *Context) -{ - ALlistener &Listener = Context->Listener; - - ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props) return false; - - /* 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)}; - 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::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); - - const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; - Listener.Params.Velocity = Listener.Params.Matrix * vel; - - Listener.Params.Gain = props->Gain * Context->GainBoost; - - AtomicReplaceHead(Context->FreeListenerProps, props); - return true; -} - -bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force) -{ - ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props && !force) return false; - - EffectState *state; - if(!props) - state = slot->Params.mEffectState; - else - { - slot->Params.Gain = props->Gain; - slot->Params.AuxSendAuto = props->AuxSendAuto; - slot->Params.Target = props->Target; - slot->Params.EffectType = props->Type; - slot->Params.mEffectProps = props->Props; - if(IsReverbEffect(props->Type)) - { - slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->Params.DecayTime = props->Props.Reverb.DecayTime; - slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; - } - else - { - slot->Params.RoomRolloff = 0.0f; - slot->Params.DecayTime = 0.0f; - slot->Params.DecayLFRatio = 0.0f; - slot->Params.DecayHFRatio = 0.0f; - slot->Params.DecayHFLimit = AL_FALSE; - slot->Params.AirAbsorptionGainHF = 1.0f; - } - - state = props->State; - props->State = nullptr; - EffectState *oldstate{slot->Params.mEffectState}; - slot->Params.mEffectState = state; - - /* Manually decrement the old effect state's refcount if it's greater - * than 1. We need to be a bit clever here to avoid the refcount - * reaching 0 since it can't be deleted in the mixer. - */ - ALuint oldval{oldstate->mRef.load(std::memory_order_acquire)}; - while(oldval > 1 && !oldstate->mRef.compare_exchange_weak(oldval, oldval-1, - std::memory_order_acq_rel, std::memory_order_acquire)) - { - /* oldval was updated with the current value on failure, so just - * try again. - */ - } - - if(oldval < 2) - { - /* Otherwise, if it would be deleted, send it off with a release - * event. - */ - RingBuffer *ring{context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(LIKELY(evt_vec.first.len > 0)) - { - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}}; - evt->u.mEffectState = oldstate; - ring->writeAdvance(1); - context->EventSem.post(); - } - else - { - /* If writing the event failed, the queue was probably full. - * Store the old state in the property object where it can - * eventually be cleaned up sometime later (not ideal, but - * better than blocking or leaking). - */ - props->State = oldstate; - } - } - - AtomicReplaceHead(context->FreeEffectslotProps, props); - } - - EffectTarget output; - if(ALeffectslot *target{slot->Params.Target}) - output = EffectTarget{&target->Wet, nullptr}; - else - { - ALCdevice *device{context->Device}; - output = EffectTarget{&device->Dry, &device->RealOut}; - } - state->update(context, slot, &slot->Params.mEffectProps, output); - return true; -} - - -/* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in - * front. - */ -inline float ScaleAzimuthFront(float azimuth, float scale) -{ - const ALfloat abs_azi{std::fabs(azimuth)}; - if(!(abs_azi > al::MathDefs<float>::Pi()*0.5f)) - return minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f) * std::copysign(1.0f, 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) -{ - static constexpr ChanMap MonoMap[1]{ - { FrontCenter, 0.0f, 0.0f } - }, RearMap[2]{ - { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 150.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) } - }; - - ChanMap StereoMap[2]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) } - }; - - const auto Frequency = static_cast<ALfloat>(Device->Frequency); - const ALsizei NumSends{Device->NumAuxSends}; - ASSUME(NumSends >= 0); - - bool DirectChannels{props->DirectChannels != AL_FALSE}; - const ChanMap *chans{nullptr}; - ALsizei num_channels{0}; - bool isbformat{false}; - ALfloat downmix_gain{1.0f}; - switch(voice->mFmtChannels) - { - case FmtMono: - chans = MonoMap; - num_channels = 1; - /* Mono buffers are never played direct. */ - DirectChannels = false; - break; - - case FmtStereo: - /* Convert counter-clockwise to clockwise. */ - StereoMap[0].angle = -props->StereoPan[0]; - StereoMap[1].angle = -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 FmtBFormat2D: - num_channels = 3; - isbformat = true; - DirectChannels = false; - break; - - case FmtBFormat3D: - num_channels = 4; - isbformat = true; - DirectChannels = false; - 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{}; - ClearArray(chandata.mDryParams.Gains.Target); - std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends, - [](SendParams ¶ms) -> void { ClearArray(params.Gains.Target); }); - }); - - voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC); - if(isbformat) - { - /* Special handling for B-Format sources. */ - - if(Distance > std::numeric_limits<float>::epsilon()) - { - /* 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) - { - /* 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)}; - - /* 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)}; - - /* 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); - } - - /* NOTE: W needs to be scaled due to FuMa normalization. */ - const ALfloat &scale0 = AmbiScale::FromFuMa[0]; - ComputePanGains(&Device->Dry, coeffs, DryGain*scale0, - voice->mChans[0].mDryParams.Gains.Target); - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i]*scale0, - 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. - */ - /* 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(); - if(!props->HeadRelative) - { - N = Listener.Params.Matrix * N; - V = Listener.Params.Matrix * V; - } - /* Build and normalize right-vector */ - alu::Vector U{aluCrossproduct(N, 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. - */ - 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 - }; - - for(ALsizei c{0};c < num_channels;c++) - { - ComputePanGains(&Device->Dry, matrix[c], DryGain, - voice->mChans[c].mDryParams.Gains.Target); - - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, matrix[c], WetGain[i], - voice->mChans[c].mWetParams[i].Gains.Target); - } - } - } - } - else if(DirectChannels) - { - /* 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(ALsizei c{0};c < num_channels;c++) - { - int idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != -1) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; - } - - /* Auxiliary sends still use normal channel panning since they mix to - * B-Format, which can't channel-match. - */ - for(ALsizei c{0};c < num_channels;c++) - { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs); - - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mChans[c].mWetParams[i].Gains.Target); - } - } - } - else if(Device->mRenderMode == HrtfRender) - { - /* Full HRTF rendering. Skip the virtual channels and render to the - * real outputs. - */ - voice->mDirect.Buffer = Device->RealOut.Buffer; - - 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)}; - - /* 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; - - /* Remaining channels use the same results as the first. */ - for(ALsizei c{1};c < num_channels;c++) - { - /* 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); - - for(ALsizei c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; - for(ALsizei 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); - } - } - } - else - { - /* Local sources on HRTF play with each channel panned to its - * relative location around the listener, providing "virtual - * speaker" responses. - */ - for(ALsizei c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; - - /* 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, - voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, - voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain; - - /* Normal panning for auxiliary sends. */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs); - - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mChans[c].mWetParams[i].Gains.Target); - } - } - } - - voice->mFlags |= VOICE_HAS_HRTF; - } - else - { - /* Non-HRTF rendering. Use normal panning to the output. */ - - if(Distance > std::numeric_limits<float>::epsilon()) - { - /* Calculate NFC filter coefficient if needed. */ - if(Device->AvgSpeakerDist > 0.0f) - { - /* 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)}; - - /* Adjust NFC filters. */ - for(ALsizei c{0};c < num_channels;c++) - voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - - voice->mFlags |= VOICE_HAS_NFC; - } - - /* 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 - { - 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); - } - - for(ALsizei c{0};c < num_channels;c++) - { - /* Special-case LFE */ - if(chans[c].channel == LFE) - { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) - { - int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel); - if(idx != -1) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; - } - continue; - } - - ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain, - voice->mChans[c].mDryParams.Gains.Target); - } - - for(ALsizei c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; - for(ALsizei 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); - } - } - } - else - { - if(Device->AvgSpeakerDist > 0.0f) - { - /* If the source distance is 0, set w0 to w1 to act as a pass- - * through. We still want to pass the signal through the - * filters so they keep an appropriate history, in case the - * source moves away from the listener. - */ - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)}; - - for(ALsizei c{0};c < num_channels;c++) - voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - - voice->mFlags |= VOICE_HAS_NFC; - } - - for(ALsizei c{0};c < num_channels;c++) - { - /* Special-case LFE */ - if(chans[c].channel == LFE) - { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) - { - int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel); - if(idx != -1) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain; - } - continue; - } - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs( - (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f) - : chans[c].angle, - chans[c].elevation, Spread, coeffs - ); - - ComputePanGains(&Device->Dry, coeffs, DryGain, - voice->mChans[c].mDryParams.Gains.Target); - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mChans[c].mWetParams[i].Gains.Target); - } - } - } - } - - { - const ALfloat hfScale{props->Direct.HFReference / Frequency}; - const ALfloat lfScale{props->Direct.LFReference / Frequency}; - const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */ - const ALfloat gainLF{maxf(DryGainLF, 0.001f)}; - - voice->mDirect.FilterType = AF_None; - if(gainHF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; - if(gainLF != 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(ALsizei c{1};c < num_channels;c++) - { - voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass); - voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass); - } - } - for(ALsizei 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)}; - - 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; - - 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(ALsizei c{1};c < num_channels;c++) - { - voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass); - voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass); - } - } -} - -void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) -{ - const ALCdevice *Device{ALContext->Device}; - ALeffectslot *SendSlots[MAX_SENDS]; - - voice->mDirect.Buffer = Device->Dry.Buffer; - for(ALsizei i{0};i < Device->NumAuxSends;i++) - { - SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->DefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) - { - SendSlots[i] = nullptr; - voice->mSend[i].Buffer = {}; - } - else - voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; - } - - /* Calculate the stepping value */ - const auto Pitch = static_cast<ALfloat>(voice->mFrequency) / - static_cast<ALfloat>(Device->Frequency) * props->Pitch; - if(Pitch > static_cast<ALfloat>(MAX_PITCH)) - voice->mStep = MAX_PITCH<<FRACTIONBITS; - else - voice->mStep = maxi(fastf2i(Pitch * FRACTIONONE), 1); - if(props->mResampler == BSinc24Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc24); - else if(props->mResampler == BSinc12Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc12); - voice->mResampler = SelectResampler(props->mResampler); - - /* Calculate gains */ - const ALlistener &Listener = ALContext->Listener; - ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)}; - DryGain *= props->Direct.Gain * Listener.Params.Gain; - DryGain = minf(DryGain, GAIN_MIX_MAX); - ALfloat DryGainHF{props->Direct.GainHF}; - ALfloat DryGainLF{props->Direct.GainLF}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALsizei i{0};i < Device->NumAuxSends;i++) - { - 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; - } - - CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, - WetGain, WetGainLF, WetGainHF, SendSlots, props, Listener, Device); -} - -void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) -{ - const ALCdevice *Device{ALContext->Device}; - const ALsizei NumSends{Device->NumAuxSends}; - const ALlistener &Listener = ALContext->Listener; - - /* 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(ALsizei i{0};i < NumSends;i++) - { - SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->DefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) - { - SendSlots[i] = nullptr; - RoomRolloff[i] = 0.0f; - DecayDistance[i] = 0.0f; - DecayLFDistance[i] = 0.0f; - DecayHFDistance[i] = 0.0f; - } - else if(SendSlots[i]->Params.AuxSendAuto) - { - RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance[i] = SendSlots[i]->Params.DecayTime * - Listener.Params.ReverbSpeedOfSound; - DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio; - DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio; - if(SendSlots[i]->Params.DecayHFLimit) - { - ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)}; - DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]); - } - } - } - else - { - /* 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; - } - - if(!SendSlots[i]) - voice->mSend[i].Buffer = {}; - else - voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; - } - - /* Transform source to listener space (convert to head relative) */ - alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f}; - alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; - alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f}; - if(props->HeadRelative == AL_FALSE) - { - /* Transform source vectors */ - Position = Listener.Params.Matrix * Position; - Velocity = Listener.Params.Matrix * Velocity; - Direction = Listener.Params.Matrix * Direction; - } - else - { - /* Offset the source velocity to be relative of the listener velocity */ - Velocity += Listener.Params.Velocity; - } - - const bool directional{Direction.normalize() > 0.0f}; - alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f}; - const ALfloat Distance{ToSource.normalize()}; - - /* Initial source gain */ - ALfloat DryGain{props->Gain}; - ALfloat DryGainHF{1.0f}; - ALfloat DryGainLF{1.0f}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALsizei i{0};i < NumSends;i++) - { - WetGain[i] = props->Gain; - WetGainHF[i] = 1.0f; - WetGainLF[i] = 1.0f; - } - - /* Calculate distance attenuation */ - ALfloat ClampedDist{Distance}; - - switch(Listener.Params.SourceDistanceModel ? - props->mDistanceModel : Listener.Params.mDistanceModel) - { - case DistanceModel::InverseClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - if(props->MaxDistance < props->RefDistance) break; - /*fall-through*/ - case DistanceModel::Inverse: - if(!(props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else - { - ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor); - if(dist > 0.0f) DryGain *= props->RefDistance / dist; - for(ALsizei i{0};i < NumSends;i++) - { - dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]); - if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist; - } - } - break; - - case DistanceModel::LinearClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - if(props->MaxDistance < props->RefDistance) break; - /*fall-through*/ - case DistanceModel::Linear: - if(!(props->MaxDistance != props->RefDistance)) - ClampedDist = props->RefDistance; - else - { - ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - DryGain *= maxf(1.0f - attn, 0.0f); - for(ALsizei i{0};i < NumSends;i++) - { - attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - WetGain[i] *= maxf(1.0f - attn, 0.0f); - } - } - break; - - case DistanceModel::ExponentClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - if(props->MaxDistance < props->RefDistance) break; - /*fall-through*/ - case DistanceModel::Exponent: - if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else - { - DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor); - for(ALsizei i{0};i < NumSends;i++) - WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]); - } - break; - - case DistanceModel::Disable: - ClampedDist = props->RefDistance; - break; - } - - /* Calculate directional soundcones */ - if(directional && props->InnerAngle < 360.0f) - { - const ALfloat Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) * - ConeScale * 2.0f)}; - - ALfloat ConeVolume, ConeHF; - if(!(Angle > props->InnerAngle)) - { - ConeVolume = 1.0f; - ConeHF = 1.0f; - } - else if(Angle < props->OuterAngle) - { - ALfloat scale = ( Angle-props->InnerAngle) / - (props->OuterAngle-props->InnerAngle); - ConeVolume = lerp(1.0f, props->OuterGain, scale); - ConeHF = lerp(1.0f, props->OuterGainHF, scale); - } - else - { - ConeVolume = props->OuterGain; - ConeHF = props->OuterGainHF; - } - - 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; } - ); - } - - /* Apply gain and frequency filters */ - DryGain = clampf(DryGain, props->MinGain, props->MaxGain); - DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX); - DryGainHF *= props->Direct.GainHF; - DryGainLF *= props->Direct.GainLF; - for(ALsizei i{0};i < NumSends;i++) - { - 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; - } - - /* Distance-based air absorption and initial send decay. */ - if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f) - { - ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor * - Listener.Params.MetersPerUnit}; - if(props->AirAbsorptionFactor > 0.0f) - { - 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(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(ALsizei i{0};i < NumSends;i++) - { - if(!(DecayDistance[i] > 0.0f)) - continue; - - 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) - { - 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); - } - } - } - } - - - /* Initial source pitch */ - ALfloat Pitch{props->Pitch}; - - /* Calculate velocity-based doppler effect */ - ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.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 ALfloat SpeedOfSound{Listener.Params.SpeedOfSound}; - if(!(vls < SpeedOfSound)) - { - /* Listener moving away from the source at the speed of sound. - * Sound waves can't catch it. - */ - Pitch = 0.0f; - } - else if(!(vss < SpeedOfSound)) - { - /* Source moving toward the listener at the speed of sound. Sound - * waves bunch up to extreme frequencies. - */ - Pitch = std::numeric_limits<float>::infinity(); - } - else - { - /* Source and listener movement is nominal. Calculate the proper - * doppler shift. - */ - Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss); - } - } - - /* 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 > static_cast<ALfloat>(MAX_PITCH)) - voice->mStep = MAX_PITCH<<FRACTIONBITS; - else - voice->mStep = maxi(fastf2i(Pitch * FRACTIONONE), 1); - if(props->mResampler == BSinc24Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc24); - else if(props->mResampler == BSinc12Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc12); - voice->mResampler = SelectResampler(props->mResampler); - - ALfloat spread{0.0f}; - if(props->Radius > Distance) - spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi(); - 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); -} - -void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force) -{ - ALvoiceProps *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props && !force) return; - - if(props) - { - voice->mProps = *props; - - AtomicReplaceHead(context->FreeVoiceProps, props); - } - - if((voice->mProps.mSpatializeMode == SpatializeAuto && voice->mFmtChannels == FmtMono) || - voice->mProps.mSpatializeMode == SpatializeOn) - CalcAttnSourceParams(voice, &voice->mProps, context); - else - CalcNonAttnSourceParams(voice, &voice->mProps, context); -} - - -void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots) -{ - IncrementRef(&ctx->UpdateCount); - if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire))) - { - bool cforce{CalcContextParams(ctx)}; - bool force{CalcListenerParams(ctx) || cforce}; - force = std::accumulate(slots->begin(), slots->end(), force, - [ctx,cforce](bool force, ALeffectslot *slot) -> bool - { return CalcEffectSlotParams(slot, ctx, cforce) | force; } - ); - - std::for_each(ctx->Voices->begin(), - ctx->Voices->begin() + ctx->VoiceCount.load(std::memory_order_acquire), - [ctx,force](ALvoice &voice) -> void - { - ALuint sid{voice.mSourceID.load(std::memory_order_acquire)}; - if(sid) CalcSourceParams(&voice, ctx, force); - } - ); - } - IncrementRef(&ctx->UpdateCount); -} - -void ProcessContext(ALCcontext *ctx, const ALsizei SamplesToDo) -{ - ASSUME(SamplesToDo > 0); - - const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)}; - - /* Process pending propery updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots); - - /* Clear auxiliary effect slot mixing buffers. */ - std::for_each(auxslots->begin(), auxslots->end(), - [SamplesToDo](ALeffectslot *slot) -> void - { - for(auto &buffer : slot->MixBuffer) - std::fill_n(buffer.begin(), SamplesToDo, 0.0f); - } - ); - - /* Process voices that have a playing source. */ - std::for_each(ctx->Voices->begin(), - ctx->Voices->begin() + ctx->VoiceCount.load(std::memory_order_acquire), - [SamplesToDo,ctx](ALvoice &voice) -> void - { - const ALvoice::State vstate{voice.mPlayState.load(std::memory_order_acquire)}; - if(vstate == ALvoice::Stopped) return; - const ALuint sid{voice.mSourceID.load(std::memory_order_relaxed)}; - if(voice.mStep < 1) return; - - MixVoice(&voice, vstate, sid, ctx, SamplesToDo); - } - ); - - /* Process effects. */ - if(auxslots->size() < 1) return; - auto slots = auxslots->data(); - auto slots_end = slots + auxslots->size(); - - /* 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; - } - return false; - }; - - *sorted_slots_end = *slots; - ++sorted_slots_end; - while(++slots != slots_end) - { - /* 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; - } - - std::for_each(sorted_slots, sorted_slots_end, - [SamplesToDo](const ALeffectslot *slot) -> void - { - EffectState *state{slot->Params.mEffectState}; - state->process(SamplesToDo, slot->Wet.Buffer.data(), - static_cast<ALsizei>(slot->Wet.Buffer.size()), state->mOutTarget); - } - ); -} - - -void ApplyStablizer(FrontStablizer *Stablizer, const al::span<FloatBufferLine> Buffer, - const ALuint lidx, const ALuint ridx, const ALuint cidx, const ALsizei 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++) - { - if(i == lidx || i == ridx) - continue; - - auto &DelayBuf = Stablizer->DelayBuf[i]; - auto buffer_end = Buffer[i].begin() + SamplesToDo; - if(LIKELY(SamplesToDo >= ALsizei{FrontStablizer::DelayLength})) - { - 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)); - } - else - { - auto delay_start = std::swap_ranges(Buffer[i].begin(), buffer_end, - std::begin(DelayBuf)); - std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf)); - } - } - - 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 &Buffer, - ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter, - ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void - { - /* Combine the delayed samples and the input samples into the temp - * buffer, in reverse. Then copy the final samples back into the delay - * buffer for next time. Note that the delay buffer's samples are - * stored backwards here. - */ - auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo; - std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end); - std::reverse_copy(Buffer.begin(), Buffer.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); - - /* 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); - - for(ALsizei i{0};i < SamplesToDo;i++) - { - ALfloat lfsum{lsplit[0][i] + rsplit[0][i]}; - ALfloat hfsum{lsplit[1][i] + rsplit[1][i]}; - ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]}; - - /* This pans the separate low- and high-frequency sums between being on - * the center channel and the left/right channels. The low-frequency - * sum is 1/3rd toward center (2/3rds on left/right) and the high- - * frequency sum is 1/4th toward center (3/4ths on left/right). These - * values can be tweaked. - */ - ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<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))}; - - /* 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; - } -} - -void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALsizei SamplesToDo, - const DistanceComp::DistData *distcomp) -{ - ASSUME(SamplesToDo > 0); - - for(auto &chanbuffer : Samples) - { - const ALfloat gain{distcomp->Gain}; - const ALsizei base{distcomp->Length}; - ALfloat *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; - ++distcomp; - - if(base < 1) - continue; - - ALfloat *inout{al::assume_aligned<16>(chanbuffer.data())}; - auto inout_end = inout + SamplesToDo; - if(LIKELY(SamplesToDo >= base)) - { - auto delay_end = std::rotate(inout, inout_end - base, inout_end); - std::swap_ranges(inout, delay_end, distbuf); - } - else - { - 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)); - } -} - -void ApplyDither(const al::span<FloatBufferLine> Samples, ALuint *dither_seed, - const ALfloat quant_scale, const ALsizei SamplesToDo) -{ - /* 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 - { - 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); - }; - std::for_each(Samples.begin(), Samples.end(), dither_channel); - *dither_seed = seed; -} - - -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ -template<typename T> -inline T SampleConv(ALfloat) noexcept; - -template<> inline ALfloat SampleConv(ALfloat val) noexcept -{ return val; } -template<> inline ALint SampleConv(ALfloat val) noexcept -{ - /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit. - * This means a normalized float has at most 25 bits of signed precision. - * When scaling and clamping for a signed 32-bit integer, these following - * values are the best a float can give. - */ - return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); -} -template<> inline ALshort SampleConv(ALfloat val) noexcept -{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); } -template<> inline ALbyte SampleConv(ALfloat val) noexcept -{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); } - -/* Define unsigned output variations. */ -template<> inline ALuint SampleConv(ALfloat val) noexcept -{ return SampleConv<ALint>(val) + 2147483648u; } -template<> inline ALushort SampleConv(ALfloat val) noexcept -{ return SampleConv<ALshort>(val) + 32768; } -template<> inline ALubyte SampleConv(ALfloat val) noexcept -{ return SampleConv<ALbyte>(val) + 128; } - -template<DevFmtType T> -void Write(const al::span<const FloatBufferLine> InBuffer, ALvoid *OutBuffer, const size_t Offset, - const ALsizei SamplesToDo) -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const size_t numchans{InBuffer.size()}; - ASSUME(numchans > 0); - - SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans; - auto conv_channel = [&outbase,SamplesToDo,numchans](const FloatBufferLine &inbuf) -> void - { - ASSUME(SamplesToDo > 0); - SampleType *out{outbase++}; - auto conv_sample = [numchans,&out](const ALfloat s) noexcept -> void - { - *out = SampleConv<SampleType>(s); - out += numchans; - }; - std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); - }; - std::for_each(InBuffer.cbegin(), InBuffer.cend(), conv_channel); -} - -} // namespace - -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples) -{ - FPUCtl mixer_mode{}; - for(ALsizei SamplesDone{0};SamplesDone < NumSamples;) - { - const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)}; - - /* Clear main mixing buffers. */ - std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(), - [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void - { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); } - ); - - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(&device->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); - - /* 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 mix count at the end (lsb should now be 0). */ - IncrementRef(&device->MixCount); - - /* Apply any needed post-process for finalizing the Dry mix to the - * RealOut (Ambisonic decode, UHJ encode, etc). - */ - if(LIKELY(device->PostProcess)) - device->PostProcess(device, SamplesToDo); - const al::span<FloatBufferLine> RealOut{device->RealOut.Buffer}; - - /* Apply front image stablization for surround sound, if applicable. */ - if(device->Stablizer) - { - const int lidx{GetChannelIdxByName(device->RealOut, FrontLeft)}; - const int ridx{GetChannelIdxByName(device->RealOut, FrontRight)}; - const int cidx{GetChannelIdxByName(device->RealOut, FrontCenter)}; - assert(lidx >= 0 && ridx >= 0 && cidx >= 0); - - ApplyStablizer(device->Stablizer.get(), RealOut, lidx, ridx, cidx, SamplesToDo); - } - - /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Compressor *comp{device->Limiter.get()}) - comp->process(SamplesToDo, RealOut.data()); - - /* Apply delays and attenuation for mismatched speaker distances. */ - ApplyDistanceComp(RealOut, SamplesToDo, device->ChannelDelay.as_span().cbegin()); - - /* 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); - - if(LIKELY(OutBuffer)) - { - /* Finally, interleave and convert samples, writing to the device's - * output buffer. - */ - switch(device->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) -#undef HANDLE_WRITE - } - } - - SamplesDone += SamplesToDo; - } -} - - -void aluHandleDisconnect(ALCdevice *device, 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; - - va_list args; - va_start(args, msg); - int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.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; - - for(ALCcontext *ctx : *device->mContexts.load()) - { - const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)}; - if((enabledevt&EventType_Disconnected)) - { - RingBuffer *ring{ctx->AsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; - if(evt_data.len > 0) - { - new (evt_data.buf) AsyncEvent{evt}; - ring->writeAdvance(1); - ctx->EventSem.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->Voices->begin(), - ctx->Voices->begin() + ctx->VoiceCount.load(std::memory_order_acquire), - stop_voice); - } -} diff --git a/Alc/alu.h b/Alc/alu.h deleted file mode 100644 index 9acf904a..00000000 --- a/Alc/alu.h +++ /dev/null @@ -1,466 +0,0 @@ -#ifndef _ALU_H_ -#define _ALU_H_ - -#include <array> -#include <atomic> -#include <cmath> -#include <cstddef> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - -#include "alBuffer.h" -#include "alcmain.h" -#include "almalloc.h" -#include "alspan.h" -#include "ambidefs.h" -#include "filters/biquad.h" -#include "filters/nfc.h" -#include "filters/splitter.h" -#include "hrtf.h" -#include "logging.h" - -struct ALbufferlistitem; -struct ALeffectslot; -struct BSincTable; - - -enum class DistanceModel; - -#define MAX_PITCH 255 -#define MAX_SENDS 16 - - -#define DITHER_RNG_SEED 22222 - - -enum SpatializeMode { - SpatializeOff = AL_FALSE, - SpatializeOn = AL_TRUE, - SpatializeAuto = AL_AUTO_SOFT -}; - -enum Resampler { - PointResampler, - LinearResampler, - FIR4Resampler, - BSinc12Resampler, - BSinc24Resampler, - - ResamplerMax = BSinc24Resampler -}; -extern Resampler ResamplerDefault; - -/* The number of distinct scale and phase intervals within the bsinc filter - * table. - */ -#define BSINC_SCALE_BITS 4 -#define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS) -#define BSINC_PHASE_BITS 4 -#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 { - ALfloat sf; /* Scale interpolation factor. */ - ALsizei m; /* Coefficient count. */ - ALsizei l; /* Left coefficient offset. */ - /* Filter coefficients, followed by the scale, phase, and scale-phase - * delta coefficients. Starting at phase index 0, each subsequent phase - * index follows contiguously. - */ - const ALfloat *filter; -}; - -union InterpState { - BsincState bsinc; -}; - -using ResamplerFunc = const ALfloat*(*)(const InterpState *state, - const ALfloat *RESTRICT src, ALsizei frac, ALint increment, - ALfloat *RESTRICT dst, ALsizei dstlen); - -void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table); - -extern const BSincTable bsinc12; -extern const BSincTable bsinc24; - - -enum { - AF_None = 0, - AF_LowPass = 1, - AF_HighPass = 2, - AF_BandPass = AF_LowPass | AF_HighPass -}; - - -struct MixHrtfFilter { - const HrirArray<ALfloat> *Coeffs; - ALsizei Delay[2]; - ALfloat Gain; - ALfloat GainStep; -}; - - -struct DirectParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - NfcFilter NFCtrlFilter; - - struct { - HrtfFilter Old; - HrtfFilter Target; - HrtfState State; - } Hrtf; - - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]; - ALfloat Target[MAX_OUTPUT_CHANNELS]; - } Gains; -}; - -struct SendParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]; - ALfloat Target[MAX_OUTPUT_CHANNELS]; - } Gains; -}; - - -struct ALvoicePropsBase { - ALfloat Pitch; - ALfloat Gain; - ALfloat OuterGain; - ALfloat MinGain; - ALfloat MaxGain; - ALfloat InnerAngle; - ALfloat OuterAngle; - ALfloat RefDistance; - ALfloat MaxDistance; - ALfloat RolloffFactor; - std::array<ALfloat,3> Position; - std::array<ALfloat,3> Velocity; - std::array<ALfloat,3> Direction; - std::array<ALfloat,3> OrientAt; - std::array<ALfloat,3> OrientUp; - ALboolean HeadRelative; - DistanceModel mDistanceModel; - Resampler mResampler; - ALboolean DirectChannels; - SpatializeMode mSpatializeMode; - - ALboolean DryGainHFAuto; - ALboolean WetGainAuto; - ALboolean WetGainHFAuto; - ALfloat OuterGainHF; - - ALfloat AirAbsorptionFactor; - ALfloat RoomRolloffFactor; - ALfloat DopplerFactor; - - std::array<ALfloat,2> StereoPan; - - ALfloat Radius; - - /** Direct filter and auxiliary send info. */ - struct { - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - } Send[MAX_SENDS]; -}; - -struct ALvoiceProps : public ALvoicePropsBase { - std::atomic<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<ALsizei> 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; - ALsizei mNumChannels; - ALsizei mSampleSize; - - /** Current target parameters used for mixing. */ - ALint mStep; - - ResamplerFunc mResampler; - - InterpState mResampleState; - - ALuint mFlags; - - struct DirectData { - int FilterType; - al::span<FloatBufferLine> Buffer; - }; - DirectData mDirect; - - struct SendData { - int FilterType; - al::span<FloatBufferLine> Buffer; - }; - std::array<SendData,MAX_SENDS> mSend; - - struct ChannelData { - alignas(16) std::array<ALfloat,MAX_RESAMPLE_PADDING*2> mPrevSamples; - - ALfloat 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() { 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; - } -}; - - -using MixerFunc = void(*)(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize); -using RowMixerFunc = void(*)(FloatBufferLine &OutBuffer, const ALfloat *gains, - const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, - const ALsizei BufferSize); -using HrtfMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfFilter *hrtfparams, const ALsizei BufferSize); -using HrtfMixerBlendFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize); -using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const ALsizei BufferSize); - - -#define GAIN_MIX_MAX (1000.0f) /* +60dB */ - -#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */ - -#define SPEEDOFSOUNDMETRESPERSEC (343.3f) -#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */ - -/* Target gain for the reverb decay feedback reaching the decay time. */ -#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */ - -#define FRACTIONBITS (12) -#define FRACTIONONE (1<<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, -}; - -void aluInit(void); - -void aluInitMixer(void); - -ResamplerFunc SelectResampler(Resampler resampler); - -/* 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); - -void ProcessHrtf(ALCdevice *device, const ALsizei SamplesToDo); -void ProcessAmbiDec(ALCdevice *device, const ALsizei SamplesToDo); -void ProcessUhj(ALCdevice *device, const ALsizei SamplesToDo); -void ProcessBs2b(ALCdevice *device, const ALsizei SamplesToDo); - -/** - * 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 ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread, - ALfloat (&coeffs)[MAX_AMBI_CHANNELS]); - -/** - * 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 ALfloat (&dir)[3], ALfloat spread, ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) -{ - /* 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(ALfloat azimuth, ALfloat elevation, ALfloat spread, ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) -{ - ALfloat x = -std::sin(azimuth) * std::cos(elevation); - ALfloat y = std::sin(elevation); - ALfloat 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 ALfloat*RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS]); - - -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 MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo); - -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples); -/* Caller must lock the device state, and the mixer must not be running. */ -void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3); - -extern MixerFunc MixSamples; -extern RowMixerFunc MixRowSamples; - -extern const ALfloat ConeScale; -extern const ALfloat ZScale; -extern const ALboolean OverrideReverbSpeedOfSound; - -#endif diff --git a/Alc/ambdec.cpp b/Alc/ambdec.cpp deleted file mode 100644 index 0991cfc5..00000000 --- a/Alc/ambdec.cpp +++ /dev/null @@ -1,436 +0,0 @@ - -#include "config.h" - -#include "ambdec.h" - -#include <cctype> -#include <cstring> -#include <algorithm> - -#include <limits> -#include <string> -#include <fstream> -#include <sstream> - -#include "logging.h" -#include "compat.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 17a9815b..00000000 --- a/Alc/ambidefs.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef AMBIDEFS_H -#define AMBIDEFS_H - -#include <array> - -/* 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<int,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<int,MAX_AMBI_CHANNELS> FromACN{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; - - static constexpr std::array<int,MAX_AMBI2D_CHANNELS> From2D{{ - 0, 1,3, 4,8, 9,15 - }}; - static constexpr std::array<int,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 deleted file mode 100644 index c133df68..00000000 --- a/Alc/backends/alsa.cpp +++ /dev/null @@ -1,1288 +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 "backends/alsa.h" - -#include <algorithm> -#include <atomic> -#include <cassert> -#include <cerrno> -#include <chrono> -#include <cstring> -#include <exception> -#include <functional> -#include <memory> -#include <string> -#include <thread> -#include <utility> - -#include "AL/al.h" - -#include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alu.h" -#include "compat.h" -#include "logging.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" - -#include <alsa/asoundlib.h> - - -namespace { - -constexpr ALCchar alsaDevice[] = "ALSA Default"; - - -#ifdef HAVE_DYNLOAD -#define ALSA_FUNCS(MAGIC) \ - MAGIC(snd_strerror); \ - MAGIC(snd_pcm_open); \ - MAGIC(snd_pcm_close); \ - MAGIC(snd_pcm_nonblock); \ - MAGIC(snd_pcm_frames_to_bytes); \ - MAGIC(snd_pcm_bytes_to_frames); \ - MAGIC(snd_pcm_hw_params_malloc); \ - MAGIC(snd_pcm_hw_params_free); \ - MAGIC(snd_pcm_hw_params_any); \ - MAGIC(snd_pcm_hw_params_current); \ - MAGIC(snd_pcm_hw_params_set_access); \ - MAGIC(snd_pcm_hw_params_set_format); \ - MAGIC(snd_pcm_hw_params_set_channels); \ - 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_current); \ - 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); \ - MAGIC(snd_pcm_reset); \ - MAGIC(snd_pcm_wait); \ - 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); \ - MAGIC(snd_pcm_writei); \ - MAGIC(snd_pcm_drain); \ - MAGIC(snd_pcm_drop); \ - MAGIC(snd_pcm_recover); \ - MAGIC(snd_pcm_info_malloc); \ - MAGIC(snd_pcm_info_free); \ - MAGIC(snd_pcm_info_set_device); \ - MAGIC(snd_pcm_info_set_subdevice); \ - MAGIC(snd_pcm_info_set_stream); \ - MAGIC(snd_pcm_info_get_name); \ - MAGIC(snd_ctl_pcm_next_device); \ - MAGIC(snd_ctl_pcm_info); \ - MAGIC(snd_ctl_open); \ - MAGIC(snd_ctl_close); \ - MAGIC(snd_ctl_card_info_malloc); \ - MAGIC(snd_ctl_card_info_free); \ - MAGIC(snd_ctl_card_info); \ - MAGIC(snd_ctl_card_info_get_name); \ - MAGIC(snd_ctl_card_info_get_id); \ - MAGIC(snd_card_next); \ - MAGIC(snd_config_update_free_global) - -static void *alsa_handle; -#define MAKE_FUNC(f) decltype(f) * p##f -ALSA_FUNCS(MAKE_FUNC); -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define snd_strerror psnd_strerror -#define snd_pcm_open psnd_pcm_open -#define snd_pcm_close psnd_pcm_close -#define snd_pcm_nonblock psnd_pcm_nonblock -#define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes -#define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames -#define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc -#define snd_pcm_hw_params_free psnd_pcm_hw_params_free -#define snd_pcm_hw_params_any psnd_pcm_hw_params_any -#define snd_pcm_hw_params_current psnd_pcm_hw_params_current -#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_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 -#define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample -#define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near -#define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near -#define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near -#define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near -#define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min -#define snd_pcm_hw_params_get_buffer_time_min psnd_pcm_hw_params_get_buffer_time_min -#define snd_pcm_hw_params_get_buffer_time_max psnd_pcm_hw_params_get_buffer_time_max -#define snd_pcm_hw_params_get_period_time_min psnd_pcm_hw_params_get_period_time_min -#define snd_pcm_hw_params_get_period_time_max psnd_pcm_hw_params_get_period_time_max -#define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size -#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_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 -#define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc -#define snd_pcm_sw_params_current psnd_pcm_sw_params_current -#define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min -#define snd_pcm_sw_params_set_stop_threshold psnd_pcm_sw_params_set_stop_threshold -#define snd_pcm_sw_params psnd_pcm_sw_params -#define snd_pcm_sw_params_free psnd_pcm_sw_params_free -#define snd_pcm_prepare psnd_pcm_prepare -#define snd_pcm_start psnd_pcm_start -#define snd_pcm_resume psnd_pcm_resume -#define snd_pcm_reset psnd_pcm_reset -#define snd_pcm_wait psnd_pcm_wait -#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 -#define snd_pcm_writei psnd_pcm_writei -#define snd_pcm_drain psnd_pcm_drain -#define snd_pcm_drop psnd_pcm_drop -#define snd_pcm_recover psnd_pcm_recover -#define snd_pcm_info_malloc psnd_pcm_info_malloc -#define snd_pcm_info_free psnd_pcm_info_free -#define snd_pcm_info_set_device psnd_pcm_info_set_device -#define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice -#define snd_pcm_info_set_stream psnd_pcm_info_set_stream -#define snd_pcm_info_get_name psnd_pcm_info_get_name -#define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device -#define snd_ctl_pcm_info psnd_ctl_pcm_info -#define snd_ctl_open psnd_ctl_open -#define snd_ctl_close psnd_ctl_close -#define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc -#define snd_ctl_card_info_free psnd_ctl_card_info_free -#define snd_ctl_card_info psnd_ctl_card_info -#define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name -#define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id -#define snd_card_next psnd_card_next -#define snd_config_update_free_global psnd_config_update_free_global -#endif -#endif - - -struct DevMap { - std::string name; - std::string device_name; -}; - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - - -const char *prefix_name(snd_pcm_stream_t stream) -{ - assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE); - return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix"; -} - -al::vector<DevMap> probe_devices(snd_pcm_stream_t stream) -{ - al::vector<DevMap> devlist; - - snd_ctl_card_info_t *info; - snd_ctl_card_info_malloc(&info); - 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")}); - - if(stream == SND_PCM_STREAM_PLAYBACK) - { - const char *customdevs; - const char *next{GetConfigValue(nullptr, "alsa", "custom-devices", "")}; - while((customdevs=next) != nullptr && customdevs[0]) - { - next = strchr(customdevs, ';'); - const char *sep{strchr(customdevs, '=')}; - if(!sep) - { - std::string spec{next ? std::string(customdevs, next++) : std::string(customdevs)}; - ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); - continue; - } - - const char *oldsep{sep++}; - devlist.emplace_back(DevMap{std::string(customdevs, oldsep), - next ? std::string(sep, next++) : std::string(sep)}); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); - } - } - - const std::string main_prefix{ - ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; - - int card{-1}; - int err{snd_card_next(&card)}; - for(;err >= 0 && card >= 0;err = snd_card_next(&card)) - { - std::string name{"hw:" + std::to_string(card)}; - - snd_ctl_t *handle; - if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0) - { - ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); - continue; - } - if((err=snd_ctl_card_info(handle, info)) < 0) - { - ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); - snd_ctl_close(handle); - continue; - } - - const char *cardname{snd_ctl_card_info_get_name(info)}; - const char *cardid{snd_ctl_card_info_get_id(info)}; - name = prefix_name(stream); - name += '-'; - name += cardid; - const std::string card_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; - - int dev{-1}; - while(1) - { - 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, dev); - snd_pcm_info_set_subdevice(pcminfo, 0); - snd_pcm_info_set_stream(pcminfo, stream); - if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) - { - if(err != -ENOENT) - ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); - continue; - } - - /* "prefix-cardid-dev" */ - name = prefix_name(stream); - name += '-'; - name += cardid; - name += '-'; - name += std::to_string(dev); - const std::string device_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)}; - - /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ - name = cardname; - name += ", "; - name += snd_pcm_info_get_name(pcminfo); - name += " (CARD="; - name += cardid; - name += ",DEV="; - name += std::to_string(dev); - name += ')'; - - /* "devprefixCARD=cardid,DEV=dev" */ - std::string device{device_prefix}; - device += "CARD="; - device += cardid; - device += ",DEV="; - device += std::to_string(dev); - - devlist.emplace_back(DevMap{std::move(name), std::move(device)}); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); - } - snd_ctl_close(handle); - } - if(err < 0) - ERR("snd_card_next failed: %s\n", snd_strerror(err)); - - snd_pcm_info_free(pcminfo); - snd_ctl_card_info_free(info); - - return devlist; -} - - -int verify_state(snd_pcm_t *handle) -{ - snd_pcm_state_t state{snd_pcm_state(handle)}; - - int err; - switch(state) - { - case SND_PCM_STATE_OPEN: - case SND_PCM_STATE_SETUP: - case SND_PCM_STATE_PREPARED: - case SND_PCM_STATE_RUNNING: - case SND_PCM_STATE_DRAINING: - case SND_PCM_STATE_PAUSED: - /* All Okay */ - break; - - case SND_PCM_STATE_XRUN: - if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0) - return err; - break; - case SND_PCM_STATE_SUSPENDED: - if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0) - return err; - break; - case SND_PCM_STATE_DISCONNECTED: - return -ENODEV; - } - - return state; -} - - -struct AlsaPlayback final : public BackendBase { - AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~AlsaPlayback() override; - - int mixerProc(); - int mixerNoMMapProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - ClockLatency getClockLatency() override; - - snd_pcm_t *mPcmHandle{nullptr}; - - al::vector<char> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(AlsaPlayback) -}; - -AlsaPlayback::~AlsaPlayback() -{ - if(mPcmHandle) - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; -} - - -int AlsaPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t num_updates{mDevice->BufferSize / update_size}; - while(!mKillNow.load(std::memory_order_acquire)) - { - int state{verify_state(mPcmHandle)}; - if(state < 0) - { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); - break; - } - - snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; - if(avail < 0) - { - ERR("available update failed: %s\n", snd_strerror(avail)); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) > update_size*(num_updates+1)) - { - WARN("available samples exceeds the buffer size\n"); - snd_pcm_reset(mPcmHandle); - continue; - } - - // make sure there's frames to process - if(static_cast<snd_pcm_uframes_t>(avail) < update_size) - { - if(state != SND_PCM_STATE_RUNNING) - { - int err{snd_pcm_start(mPcmHandle)}; - if(err < 0) - { - ERR("start failed: %s\n", snd_strerror(err)); - continue; - } - } - if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); - continue; - } - avail -= avail%update_size; - - // it is possible that contiguous areas are smaller, thus we use a loop - lock(); - while(avail > 0) - { - snd_pcm_uframes_t frames{static_cast<snd_pcm_uframes_t>(avail)}; - - const snd_pcm_channel_area_t *areas{}; - snd_pcm_uframes_t offset{}; - int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; - if(err < 0) - { - ERR("mmap begin error: %s\n", snd_strerror(err)); - break; - } - - char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)}; - aluMixData(mDevice, WritePtr, frames); - - snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; - if(commitres < 0 || (commitres-frames) != 0) - { - ERR("mmap commit error: %s\n", - snd_strerror(commitres >= 0 ? -EPIPE : commitres)); - break; - } - - avail -= frames; - } - unlock(); - } - - return 0; -} - -int AlsaPlayback::mixerNoMMapProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; - while(!mKillNow.load(std::memory_order_acquire)) - { - int state{verify_state(mPcmHandle)}; - if(state < 0) - { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); - break; - } - - snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; - if(avail < 0) - { - ERR("available update failed: %s\n", snd_strerror(avail)); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size) - { - WARN("available samples exceeds the buffer size\n"); - snd_pcm_reset(mPcmHandle); - continue; - } - - if(static_cast<snd_pcm_uframes_t>(avail) < update_size) - { - if(state != SND_PCM_STATE_RUNNING) - { - int err{snd_pcm_start(mPcmHandle)}; - if(err < 0) - { - ERR("start failed: %s\n", snd_strerror(err)); - continue; - } - } - if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); - continue; - } - - lock(); - char *WritePtr{mBuffer.data()}; - avail = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - aluMixData(mDevice, WritePtr, avail); - while(avail > 0) - { - snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, avail)}; - switch(ret) - { - case -EAGAIN: - continue; -#if ESTRPIPE != EPIPE - case -ESTRPIPE: -#endif - case -EPIPE: - case -EINTR: - ret = snd_pcm_recover(mPcmHandle, ret, 1); - if(ret < 0) - avail = 0; - break; - default: - if(ret >= 0) - { - WritePtr += snd_pcm_frames_to_bytes(mPcmHandle, ret); - avail -= ret; - } - break; - } - if(ret < 0) - { - ret = snd_pcm_prepare(mPcmHandle); - if(ret < 0) break; - } - } - unlock(); - } - - return 0; -} - - -ALCenum AlsaPlayback::open(const ALCchar *name) -{ - const char *driver{}; - 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; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - driver = iter->device_name.c_str(); - } - else - { - name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "device", "default"); - } - - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; - if(err < 0) - { - ERR("Could not open playback device '%s': %s\n", driver, snd_strerror(err)); - return ALC_OUT_OF_MEMORY; - } - - /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ - snd_config_update_free_global(); - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean AlsaPlayback::reset() -{ - snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; - switch(mDevice->FmtType) - { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; - } - - 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}; - - snd_pcm_uframes_t periodSizeInFrames{}; - snd_pcm_uframes_t bufferSizeInFrames{}; - snd_pcm_sw_params_t *sp{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access{}; - const char *funcerr{}; - int err{}; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); - /* set interleaved access */ - if(!allowmmap || snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) - { - /* No mmap */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); - } - /* test and set format (implicitly sets sample bits) */ - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) < 0) - { - static const struct { - snd_pcm_format_t format; - DevFmtType fmttype; - } formatlist[] = { - { SND_PCM_FORMAT_FLOAT, DevFmtFloat }, - { SND_PCM_FORMAT_S32, DevFmtInt }, - { SND_PCM_FORMAT_U32, DevFmtUInt }, - { SND_PCM_FORMAT_S16, DevFmtShort }, - { SND_PCM_FORMAT_U16, DevFmtUShort }, - { SND_PCM_FORMAT_S8, DevFmtByte }, - { SND_PCM_FORMAT_U8, DevFmtUByte }, - }; - - for(const auto &fmt : formatlist) - { - format = fmt.format; - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) >= 0) - { - mDevice->FmtType = fmt.fmttype; - break; - } - } - } - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); - /* test and set channels (implicitly sets frame bits) */ - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, mDevice->channelsFromFmt()) < 0) - { - static const DevFmtChannels channellist[] = { - DevFmtStereo, - DevFmtQuad, - DevFmtX51, - DevFmtX71, - DevFmtMono, - }; - - for(const auto &chan : channellist) - { - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, ChannelsFromDevFmt(chan, 0)) >= 0) - { - mDevice->FmtChans = chan; - mDevice->mAmbiOrder = 0; - break; - } - } - } - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); - /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) || - !mDevice->Flags.get<FrequencyRequest>()) - { - if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 0) < 0) - ERR("Failed to disable ALSA resampler\n"); - } - else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 1) < 0) - ERR("Failed to enable ALSA resampler\n"); - CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp, &rate, nullptr)); - /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp, &periodLen, nullptr)) < 0) - ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); - /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp, &bufferLen, nullptr)) < 0) - ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); - /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); - - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); - CHECK(snd_pcm_hw_params_get_buffer_size(hp, &bufferSizeInFrames)); - snd_pcm_hw_params_free(hp); - hp = nullptr; - - snd_pcm_sw_params_malloc(&sp); - CHECK(snd_pcm_sw_params_current(mPcmHandle, sp)); - CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp, periodSizeInFrames)); - CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp, bufferSizeInFrames)); - CHECK(snd_pcm_sw_params(mPcmHandle, sp)); -#undef CHECK - snd_pcm_sw_params_free(sp); - sp = nullptr; - - mDevice->BufferSize = bufferSizeInFrames; - mDevice->UpdateSize = periodSizeInFrames; - mDevice->Frequency = rate; - - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; - -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - if(sp) snd_pcm_sw_params_free(sp); - return ALC_FALSE; -} - -ALCboolean AlsaPlayback::start() -{ - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access; - const char *funcerr; - int err; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_current(mPcmHandle, hp)); - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); -#undef CHECK - if(0) - { - error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - return ALC_FALSE; - } - snd_pcm_hw_params_free(hp); - hp = nullptr; - - int (AlsaPlayback::*thread_func)(){}; - if(access == SND_PCM_ACCESS_RW_INTERLEAVED) - { - mBuffer.resize(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)); - thread_func = &AlsaPlayback::mixerNoMMapProc; - } - else - { - err = snd_pcm_prepare(mPcmHandle); - if(err < 0) - { - ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err)); - return ALC_FALSE; - } - thread_func = &AlsaPlayback::mixerProc; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(thread_func), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - mBuffer.clear(); - return ALC_FALSE; -} - -void AlsaPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - mBuffer.clear(); -} - -ClockLatency AlsaPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - snd_pcm_sframes_t delay{}; - int err{snd_pcm_delay(mPcmHandle, &delay)}; - if(err < 0) - { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); - delay = 0; - } - ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct AlsaCapture final : public BackendBase { - AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~AlsaCapture() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - ClockLatency getClockLatency() override; - - snd_pcm_t *mPcmHandle{nullptr}; - - al::vector<char> mBuffer; - - bool mDoCapture{false}; - RingBufferPtr mRing{nullptr}; - - snd_pcm_sframes_t mLastAvail{0}; - - DEF_NEWDEL(AlsaCapture) -}; - -AlsaCapture::~AlsaCapture() -{ - if(mPcmHandle) - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; -} - - -ALCenum AlsaCapture::open(const ALCchar *name) -{ - const char *driver{}; - 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; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - driver = iter->device_name.c_str(); - } - else - { - name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "capture", "default"); - } - - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; - if(err < 0) - { - ERR("Could not open capture device '%s': %s\n", driver, snd_strerror(err)); - return ALC_INVALID_VALUE; - } - - /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ - snd_config_update_free_global(); - - snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; - switch(mDevice->FmtType) - { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; - } - - snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(bufferSizeInFrames, 25*mDevice->Frequency/1000)}; - - bool needring{false}; - const char *funcerr{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); - /* set interleaved access */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); - /* set format (implicitly sets sample bits) */ - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); - /* set channels (implicitly sets frame bits) */ - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); - /* set rate (implicitly constrains period/buffer parameters) */ - CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp, mDevice->Frequency, 0)); - /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp, &bufferSizeInFrames) < 0) - { - TRACE("Buffer too large, using intermediate ring buffer\n"); - needring = true; - CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp, &bufferSizeInFrames)); - } - /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp, &periodSizeInFrames, nullptr)); - /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); - /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); -#undef CHECK - snd_pcm_hw_params_free(hp); - hp = nullptr; - - if(needring) - { - mRing = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("ring buffer create failed\n"); - goto error2; - } - } - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; - -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - -error2: - mRing = nullptr; - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; - - return ALC_INVALID_VALUE; -} - - -ALCboolean AlsaCapture::start() -{ - int err{snd_pcm_prepare(mPcmHandle)}; - if(err < 0) - ERR("prepare failed: %s\n", snd_strerror(err)); - else - { - err = snd_pcm_start(mPcmHandle); - if(err < 0) - ERR("start failed: %s\n", snd_strerror(err)); - } - if(err < 0) - { - aluHandleDisconnect(mDevice, "Capture state failure: %s", snd_strerror(err)); - return ALC_FALSE; - } - - mDoCapture = true; - return ALC_TRUE; -} - -void AlsaCapture::stop() -{ - /* OpenAL requires access to unread audio after stopping, but ALSA's - * 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()}; - if(!mRing && avail > 0) - { - /* The ring buffer implicitly captures when checking availability. - * Direct access needs to explicitly capture it into temp storage. */ - al::vector<char> temp(snd_pcm_frames_to_bytes(mPcmHandle, avail)); - captureSamples(temp.data(), avail); - mBuffer = std::move(temp); - } - int err{snd_pcm_drop(mPcmHandle)}; - if(err < 0) - ERR("drop failed: %s\n", snd_strerror(err)); - mDoCapture = false; -} - -ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - if(mRing) - { - mRing->read(buffer, samples); - return ALC_NO_ERROR; - } - - mLastAvail -= samples; - while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0) - { - snd_pcm_sframes_t amt{0}; - - if(!mBuffer.empty()) - { - /* First get any data stored from the last stop */ - amt = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples; - - amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); - memcpy(buffer, mBuffer.data(), amt); - - mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); - amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); - } - else if(mDoCapture) - amt = snd_pcm_readi(mPcmHandle, buffer, samples); - if(amt < 0) - { - ERR("read error: %s\n", snd_strerror(amt)); - - if(amt == -EAGAIN) - continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) - { - amt = snd_pcm_start(mPcmHandle); - if(amt >= 0) - amt = snd_pcm_avail_update(mPcmHandle); - } - if(amt < 0) - { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); - break; - } - /* If the amount available is less than what's asked, we lost it - * during recovery. So just give silence instead. */ - if(static_cast<snd_pcm_uframes_t>(amt) < samples) - break; - continue; - } - - buffer = static_cast<ALbyte*>(buffer) + amt; - samples -= amt; - } - if(samples > 0) - memset(buffer, ((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0), - snd_pcm_frames_to_bytes(mPcmHandle, samples)); - - return ALC_NO_ERROR; -} - -ALCuint AlsaCapture::availableSamples() -{ - snd_pcm_sframes_t avail{0}; - if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture) - avail = snd_pcm_avail_update(mPcmHandle); - if(avail < 0) - { - ERR("avail update failed: %s\n", snd_strerror(avail)); - - if((avail=snd_pcm_recover(mPcmHandle, avail, 1)) >= 0) - { - if(mDoCapture) - avail = snd_pcm_start(mPcmHandle); - if(avail >= 0) - avail = snd_pcm_avail_update(mPcmHandle); - } - if(avail < 0) - { - ERR("restore error: %s\n", snd_strerror(avail)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(avail)); - } - } - - if(!mRing) - { - if(avail < 0) avail = 0; - avail += snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - if(avail > mLastAvail) mLastAvail = avail; - return mLastAvail; - } - - while(avail > 0) - { - auto vec = mRing->getWriteVector(); - if(vec.first.len == 0) break; - - snd_pcm_sframes_t amt{std::min<snd_pcm_sframes_t>(vec.first.len, avail)}; - amt = snd_pcm_readi(mPcmHandle, vec.first.buf, amt); - if(amt < 0) - { - ERR("read error: %s\n", snd_strerror(amt)); - - if(amt == -EAGAIN) - continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) - { - if(mDoCapture) - amt = snd_pcm_start(mPcmHandle); - if(amt >= 0) - amt = snd_pcm_avail_update(mPcmHandle); - } - if(amt < 0) - { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); - break; - } - avail = amt; - continue; - } - - mRing->writeAdvance(amt); - avail -= amt; - } - - return mRing->readSpace(); -} - -ClockLatency AlsaCapture::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - snd_pcm_sframes_t delay{}; - int err{snd_pcm_delay(mPcmHandle, &delay)}; - if(err < 0) - { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); - delay = 0; - } - ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - -} // namespace - - -bool AlsaBackendFactory::init() -{ - bool error{false}; - -#ifdef HAVE_DYNLOAD - if(!alsa_handle) - { - std::string missing_funcs; - - alsa_handle = LoadLib("libasound.so.2"); - if(!alsa_handle) - { - WARN("Failed to load %s\n", "libasound.so.2"); - return ALC_FALSE; - } - - error = ALC_FALSE; -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ -} while(0) - ALSA_FUNCS(LOAD_FUNC); -#undef LOAD_FUNC - - if(error) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(alsa_handle); - alsa_handle = nullptr; - } - } -#endif - - return !error; -} - -bool AlsaBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void AlsaBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - switch(type) - { - case DevProbe::Playback: - PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new AlsaPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new AlsaCapture{device}}; - return nullptr; -} - -BackendFactory &AlsaBackendFactory::getFactory() -{ - static AlsaBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/alsa.h b/Alc/backends/alsa.h deleted file mode 100644 index fb9de006..00000000 --- a/Alc/backends/alsa.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_ALSA_H -#define BACKENDS_ALSA_H - -#include "backends/base.h" - -struct AlsaBackendFactory 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_ALSA_H */ diff --git a/Alc/backends/base.cpp b/Alc/backends/base.cpp deleted file mode 100644 index a7d47c6d..00000000 --- a/Alc/backends/base.cpp +++ /dev/null @@ -1,58 +0,0 @@ - -#include "config.h" - -#include <cstdlib> - -#include <thread> - -#include "alcmain.h" -#include "alu.h" - -#include "backends/base.h" - - -ClockLatency GetClockLatency(ALCdevice *device) -{ - BackendBase *backend{device->Backend.get()}; - ClockLatency ret{backend->getClockLatency()}; - ret.Latency += device->FixedLatency; - return ret; -} - - -/* BackendBase method implementations. */ -BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device} -{ } - -BackendBase::~BackendBase() = default; - -ALCboolean BackendBase::reset() -{ return ALC_FALSE; } - -ALCenum BackendBase::captureSamples(void*, ALCuint) -{ return ALC_INVALID_DEVICE; } - -ALCuint BackendBase::availableSamples() -{ return 0; } - -ClockLatency BackendBase::getClockLatency() -{ - ClockLatency ret; - - ALuint refcount; - do { - while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); - ret.ClockTime = GetDeviceClockTime(mDevice); - std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != mDevice->MixCount.load(std::memory_order_relaxed)); - - /* NOTE: The device will generally have about all but one periods filled at - * any given time during playback. Without a more accurate measurement from - * the output, this is an okay approximation. - */ - ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)}; - ret.Latency /= mDevice->Frequency; - - return ret; -} diff --git a/Alc/backends/base.h b/Alc/backends/base.h deleted file mode 100644 index 437e31d9..00000000 --- a/Alc/backends/base.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef ALC_BACKENDS_BASE_H -#define ALC_BACKENDS_BASE_H - -#include <memory> -#include <chrono> -#include <string> -#include <mutex> - -#include "alcmain.h" - - -struct ClockLatency { - std::chrono::nanoseconds ClockTime; - std::chrono::nanoseconds Latency; -}; - -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - -ClockLatency GetClockLatency(ALCdevice *device); - -struct BackendBase { - virtual ALCenum open(const ALCchar *name) = 0; - - virtual ALCboolean reset(); - virtual ALCboolean start() = 0; - virtual void stop() = 0; - - virtual ALCenum captureSamples(void *buffer, ALCuint samples); - virtual ALCuint availableSamples(); - - virtual ClockLatency getClockLatency(); - - virtual void lock() { mMutex.lock(); } - virtual void unlock() { mMutex.unlock(); } - - ALCdevice *mDevice; - - std::recursive_mutex mMutex; - - BackendBase(ALCdevice *device) noexcept; - virtual ~BackendBase(); -}; -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 -}; - - -struct BackendFactory { - virtual bool init() = 0; - - virtual bool querySupport(BackendType type) = 0; - - virtual void probe(DevProbe type, std::string *outnames) = 0; - - virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; -}; - -#endif /* ALC_BACKENDS_BASE_H */ diff --git a/Alc/backends/coreaudio.cpp b/Alc/backends/coreaudio.cpp deleted file mode 100644 index b4b46382..00000000 --- a/Alc/backends/coreaudio.cpp +++ /dev/null @@ -1,709 +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 "backends/coreaudio.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "alcmain.h" -#include "alu.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"; - - -struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~CoreAudioPlayback() override; - - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - AudioUnit mAudioUnit; - - ALuint mFrameSize{0u}; - AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - DEF_NEWDEL(CoreAudioPlayback) -}; - -CoreAudioPlayback::~CoreAudioPlayback() -{ - AudioUnitUninitialize(mAudioUnit); - AudioComponentInstanceDispose(mAudioUnit); -} - - -OSStatus CoreAudioPlayback::MixerProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) -{ - return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp*, UInt32, UInt32, AudioBufferList *ioData) -{ - lock(); - aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize); - unlock(); - return noErr; -} - - -ALCenum CoreAudioPlayback::open(const ALCchar *name) -{ - if(!name) - name = ca_device; - else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; - - /* open the default output unit */ - AudioComponentDescription desc{}; - desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; -#else - desc.componentSubType = kAudioUnitSubType_DefaultOutput; -#endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; - if(comp == nullptr) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } - - OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; - if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } - - /* init and start the default audio unit... */ - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - AudioComponentInstanceDispose(mAudioUnit); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean CoreAudioPlayback::reset() -{ - OSStatus err{AudioUnitUninitialize(mAudioUnit)}; - if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); - - /* retrieve default output unit's properties (output side) */ - AudioStreamBasicDescription streamFormat{}; - auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription)); - err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 0, &streamFormat, &size); - if(err != noErr || size != sizeof(AudioStreamBasicDescription)) - { - ERR("AudioUnitGetProperty failed\n"); - return ALC_FALSE; - } - -#if 0 - TRACE("Output streamFormat of default output unit -\n"); - TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket); - TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame); - TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel); - TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket); - TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame); - TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); -#endif - - /* set default output unit's input side to match output side */ - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, size); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - if(mDevice->Frequency != streamFormat.mSampleRate) - { - mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} * - streamFormat.mSampleRate / mDevice->Frequency); - mDevice->Frequency = 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); - - /* use channel count and sample rate from the default output unit's current - * parameters, but reset everything else */ - streamFormat.mFramesPerPacket = 1; - streamFormat.mFormatFlags = 0; - switch(mDevice->FmtType) - { - case DevFmtUByte: - mDevice->FmtType = DevFmtByte; - /* fall-through */ - case DevFmtByte: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 8; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 16; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - streamFormat.mBitsPerChannel = 32; - break; - case DevFmtFloat: - 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; - - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(AudioStreamBasicDescription)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - /* setup callback */ - mFrameSize = mDevice->frameSizeFromFmt(); - AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; - input.inputProcRefCon = this; - - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - - /* init the default audio unit... */ - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - return ALC_FALSE; - } - - return ALC_TRUE; -} - -ALCboolean CoreAudioPlayback::start() -{ - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; - if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void CoreAudioPlayback::stop() -{ - OSStatus err{AudioOutputUnitStop(mAudioUnit)}; - if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); -} - - -struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~CoreAudioCapture() override; - - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); - OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, - UInt32 inNumberFrames, AudioBufferList *ioData); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - AudioUnit mAudioUnit{0}; - - ALuint mFrameSize{0u}; - AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - SampleConverterPtr mConverter; - - RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(CoreAudioCapture) -}; - -CoreAudioCapture::~CoreAudioCapture() -{ - if(mAudioUnit) - AudioComponentInstanceDispose(mAudioUnit); - mAudioUnit = 0; -} - - -OSStatus CoreAudioCapture::RecordProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) -{ - return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames, - AudioBufferList*) -{ - AudioUnitRenderActionFlags flags = 0; - union { - ALbyte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; - AudioBufferList list; - } audiobuf = { { 0 } }; - - auto rec_vec = mRing->getWriteVector(); - inNumberFrames = minz(inNumberFrames, rec_vec.first.len+rec_vec.second.len); - - // Fill the ringbuffer's two segments with data from the input device - if(rec_vec.first.len >= inNumberFrames) - { - 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 size_t remaining{inNumberFrames-rec_vec.first.len}; - audiobuf.list.mNumberBuffers = 2; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = rec_vec.first.len * mFormat.mBytesPerFrame; - audiobuf.list.mBuffers[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, - inNumberFrames, &audiobuf.list)}; - if(err != noErr) - { - ERR("AudioUnitRender error: %d\n", err); - return err; - } - - mRing->writeAdvance(inNumberFrames); - return noErr; -} - - -ALCenum CoreAudioCapture::open(const ALCchar *name) -{ - AudioStreamBasicDescription requestedFormat; // The application requested format - AudioStreamBasicDescription hardwareFormat; // The hardware format - AudioStreamBasicDescription outputFormat; // The AudioUnit output format - AURenderCallbackStruct input; - AudioComponentDescription desc; - UInt32 outputFrameCount; - UInt32 propertySize; - AudioObjectPropertyAddress propertyAddress; - UInt32 enableIO; - AudioComponent comp; - OSStatus err; - - if(!name) - name = ca_device; - else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; - - desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; -#else - desc.componentSubType = kAudioUnitSubType_HALOutput; -#endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - // Search for component with given description - comp = AudioComponentFindNext(NULL, &desc); - if(comp == NULL) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } - - // Open the component - err = AudioComponentInstanceNew(comp, &mAudioUnit); - if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } - - // Turn off AudioUnit output - enableIO = 0; - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Turn on AudioUnit input - enableIO = 1; - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - -#if !TARGET_OS_IOS - { - // Get the default input device - AudioDeviceID inputDevice = kAudioDeviceUnknown; - - propertySize = sizeof(AudioDeviceID); - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &inputDevice); - if(err != noErr) - { - ERR("AudioObjectGetPropertyData failed\n"); - return ALC_INVALID_VALUE; - } - if(inputDevice == kAudioDeviceUnknown) - { - ERR("No input device found\n"); - return ALC_INVALID_VALUE; - } - - // Track the input device - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - } -#endif - - // set capture callback - input.inputProc = CoreAudioCapture::RecordProcC; - input.inputProcRefCon = this; - - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Initialize the device - err = AudioUnitInitialize(mAudioUnit); - if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - return ALC_INVALID_VALUE; - } - - // Get the hardware format - propertySize = sizeof(AudioStreamBasicDescription); - err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 1, &hardwareFormat, &propertySize); - if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) - { - ERR("AudioUnitGetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Set up the requested format description - switch(mDevice->FmtType) - { - case DevFmtUByte: - requestedFormat.mBitsPerChannel = 8; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtShort: - requestedFormat.mBitsPerChannel = 16; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtInt: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtFloat: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - - switch(mDevice->FmtChans) - { - case DevFmtMono: - requestedFormat.mChannelsPerFrame = 1; - break; - case DevFmtStereo: - requestedFormat.mChannelsPerFrame = 2; - break; - - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - ERR("%s not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_VALUE; - } - - requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; - requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; - requestedFormat.mSampleRate = mDevice->Frequency; - requestedFormat.mFormatID = kAudioFormatLinearPCM; - requestedFormat.mReserved = 0; - requestedFormat.mFramesPerPacket = 1; - - // save requested format description for later use - mFormat = requestedFormat; - mFrameSize = mDevice->frameSizeFromFmt(); - - // Use intermediate format for sample rate conversion (outputFormat) - // Set sample rate to the same as hardware for resampling later - outputFormat = requestedFormat; - outputFormat.mSampleRate = hardwareFormat.mSampleRate; - - // The output format should be the requested format, but using the hardware sample rate - // This is because the AudioUnit will automatically scale other properties, except for sample rate - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 1, (void*)&outputFormat, sizeof(outputFormat)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Set the AudioUnit output format frame count - uint64_t FrameCount64{mDevice->UpdateSize}; - FrameCount64 = (FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) / - mDevice->Frequency; - FrameCount64 += MAX_RESAMPLE_PADDING*2; - if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2) - { - ERR("FrameCount too large\n"); - return ALC_INVALID_VALUE; - } - - outputFrameCount = static_cast<uint32_t>(FrameCount64); - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed: %d\n", err); - return ALC_INVALID_VALUE; - } - - // Set up sample converter if needed - if(outputFormat.mSampleRate != mDevice->Frequency) - mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, - mFormat.mChannelsPerFrame, hardwareFormat.mSampleRate, mDevice->Frequency, - BSinc24Resampler); - - mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false); - if(!mRing) return ALC_INVALID_VALUE; - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - - -ALCboolean CoreAudioCapture::start() -{ - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; - if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void CoreAudioCapture::stop() -{ - OSStatus err{AudioOutputUnitStop(mAudioUnit)}; - if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); -} - -ALCenum CoreAudioCapture::captureSamples(void *buffer, ALCuint samples) -{ - if(!mConverter) - { - mRing->read(buffer, samples); - return ALC_NO_ERROR; - } - - auto rec_vec = mRing->getReadVector(); - const void *src0{rec_vec.first.buf}; - auto src0len = static_cast<ALsizei>(rec_vec.first.len); - auto got = static_cast<ALuint>(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<ALsizei>(rec_vec.second.len); - got += static_cast<ALuint>(mConverter->convert(&src1, &src1len, - static_cast<char*>(buffer)+got, samples-got)); - total_read += rec_vec.second.len - src1len; - } - - mRing->readAdvance(total_read); - return ALC_NO_ERROR; -} - -ALCuint CoreAudioCapture::availableSamples() -{ - if(!mConverter) return mRing->readSpace(); - return mConverter->availableOut(mRing->readSpace()); -} - -} // namespace - -BackendFactory &CoreAudioBackendFactory::getFactory() -{ - static CoreAudioBackendFactory factory{}; - return factory; -} - -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) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(ca_device, sizeof(ca_device)); - break; - } -} - -BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new CoreAudioPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CoreAudioCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/coreaudio.h b/Alc/backends/coreaudio.h deleted file mode 100644 index 37b9ebe5..00000000 --- a/Alc/backends/coreaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_COREAUDIO_H -#define BACKENDS_COREAUDIO_H - -#include "backends/base.h" - -struct CoreAudioBackendFactory 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_COREAUDIO_H */ diff --git a/Alc/backends/dsound.cpp b/Alc/backends/dsound.cpp deleted file mode 100644 index 5a156d54..00000000 --- a/Alc/backends/dsound.cpp +++ /dev/null @@ -1,938 +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 "backends/dsound.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <cguid.h> -#include <mmreg.h> -#ifndef _WAVEFORMATEXTENSIBLE_ -#include <ks.h> -#include <ksmedia.h> -#endif - -#include <atomic> -#include <cassert> -#include <thread> -#include <string> -#include <vector> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "compat.h" -#include "threads.h" - -/* MinGW-w64 needs this for some unknown reason now. */ -using LPCWAVEFORMATEX = const WAVEFORMATEX*; -#include <dsound.h> - - -#ifndef DSSPEAKER_5POINT1 -# define DSSPEAKER_5POINT1 0x00000006 -#endif -#ifndef DSSPEAKER_5POINT1_BACK -# define DSSPEAKER_5POINT1_BACK 0x00000006 -#endif -#ifndef DSSPEAKER_7POINT1 -# define DSSPEAKER_7POINT1 0x00000007 -#endif -#ifndef DSSPEAKER_7POINT1_SURROUND -# define DSSPEAKER_7POINT1_SURROUND 0x00000008 -#endif -#ifndef DSSPEAKER_5POINT1_SURROUND -# define DSSPEAKER_5POINT1_SURROUND 0x00000009 -#endif - - -/* Some headers seem to define these as macros for __uuidof, which is annoying - * since some headers don't declare them at all. Hopefully the ifdef is enough - * to tell if they need to be declared. - */ -#ifndef KSDATAFORMAT_SUBTYPE_PCM -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif -#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif - -namespace { - -#define DEVNAME_HEAD "OpenAL Soft on " - - -#ifdef HAVE_DYNLOAD -void *ds_handle; -HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); -HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); -HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter); -HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); - -#ifndef IN_IDE_PARSER -#define DirectSoundCreate pDirectSoundCreate -#define DirectSoundEnumerateW pDirectSoundEnumerateW -#define DirectSoundCaptureCreate pDirectSoundCaptureCreate -#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW -#endif -#endif - - -#define MAX_UPDATES 128 - -struct DevMap { - std::string name; - GUID guid; - - template<typename T0, typename T1> - DevMap(T0&& name_, T1&& guid_) - : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)} - { } -}; - -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; - -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(); -} - -BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) -{ - if(!guid) - return TRUE; - - auto& devices = *static_cast<al::vector<DevMap>*>(data); - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; - - int count{1}; - std::string newname{basename}; - while(checkName(devices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - devices.emplace_back(std::move(newname), *guid); - const DevMap &newentry = devices.back(); - - OLECHAR *guidstr{nullptr}; - HRESULT hr{StringFromCLSID(*guid, &guidstr)}; - if(SUCCEEDED(hr)) - { - TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr); - CoTaskMemFree(guidstr); - } - - return TRUE; -} - - -struct DSoundPlayback final : public BackendBase { - DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~DSoundPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - IDirectSound *mDS{nullptr}; - IDirectSoundBuffer *mPrimaryBuffer{nullptr}; - IDirectSoundBuffer *mBuffer{nullptr}; - IDirectSoundNotify *mNotifies{nullptr}; - HANDLE mNotifyEvent{nullptr}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(DSoundPlayback) -}; - -DSoundPlayback::~DSoundPlayback() -{ - if(mNotifies) - mNotifies->Release(); - mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); - mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - - if(mDS) - mDS->Release(); - mDS = nullptr; - if(mNotifyEvent) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int DSoundPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - DSBCAPS DSBCaps{}; - DSBCaps.dwSize = sizeof(DSBCaps); - HRESULT err{mBuffer->GetCaps(&DSBCaps)}; - if(FAILED(err)) - { - ERR("Failed to get buffer caps: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err); - return 1; - } - - ALsizei 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)) - { - // Get current play cursor - DWORD PlayCursor; - mBuffer->GetCurrentPosition(&PlayCursor, nullptr); - DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes; - - if(avail < FragSize) - { - if(!Playing) - { - err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); - if(FAILED(err)) - { - ERR("Failed to play buffer: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err); - return 1; - } - Playing = true; - } - - avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); - if(avail != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); - continue; - } - avail -= avail%FragSize; - - // Lock output buffer - void *WritePtr1, *WritePtr2; - DWORD WriteCnt1{0u}, WriteCnt2{0u}; - err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); - - // If the buffer is lost, restore it and lock - if(err == DSERR_BUFFERLOST) - { - WARN("Buffer lost, restoring...\n"); - err = mBuffer->Restore(); - if(SUCCEEDED(err)) - { - Playing = false; - LastCursor = 0; - err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, - &WritePtr2, &WriteCnt2, 0); - } - } - - if(SUCCEEDED(err)) - { - lock(); - aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize); - if(WriteCnt2 > 0) - aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize); - unlock(); - - mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); - } - else - { - ERR("Buffer lock error: %#lx\n", err); - aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err); - return 1; - } - - // Update old write cursor location - LastCursor += WriteCnt1+WriteCnt2; - LastCursor %= DSBCaps.dwBufferBytes; - } - - return 0; -} - -ALCenum DSoundPlayback::open(const ALCchar *name) -{ - HRESULT hr; - if(PlaybackDevices.empty()) - { - /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); - } - - const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) - { - name = PlaybackDevices[0].name.c_str(); - guid = &PlaybackDevices[0].guid; - } - else - { - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - guid = &iter->guid; - } - - hr = DS_OK; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(!mNotifyEvent) hr = E_FAIL; - - //DirectSound Init code - if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, &mDS, nullptr); - if(SUCCEEDED(hr)) - hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); - if(FAILED(hr)) - { - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean 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: - break; - } - - WAVEFORMATEXTENSIBLE OutputType{}; - DWORD speakers; - HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; - if(SUCCEEDED(hr)) - { - 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); - - 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; - } - -retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = mDevice->channelsFromFmt(); - OutputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = 0; - } - - if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - if(mDevice->FmtType == DevFmtFloat) - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - } - else - { - if(SUCCEEDED(hr) && !mPrimaryBuffer) - { - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); - } - if(SUCCEEDED(hr)) - hr = mPrimaryBuffer->SetFormat(&OutputType.Format); - } - - if(SUCCEEDED(hr)) - { - ALuint 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.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; - DSBDescription.lpwfxFormat = &OutputType.Format; - - hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); - if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - } - - if(SUCCEEDED(hr)) - { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); - if(SUCCEEDED(hr)) - { - auto Notifies = static_cast<IDirectSoundNotify*>(ptr); - mNotifies = Notifies; - - ALuint 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) - { - nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; - nots[i].hEventNotify = mNotifyEvent; - } - if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) - hr = E_FAIL; - } - } - - if(FAILED(hr)) - { - if(mNotifies) - mNotifies->Release(); - mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); - mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); - mPrimaryBuffer = nullptr; - return ALC_FALSE; - } - - ResetEvent(mNotifyEvent); - SetDefaultWFXChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean DSoundPlayback::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void DSoundPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - mBuffer->Stop(); -} - - -struct DSoundCapture final : public BackendBase { - DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~DSoundCapture() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - IDirectSoundCapture *mDSC{nullptr}; - IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; - DWORD mBufferBytes{0u}; - DWORD mCursor{0u}; - - RingBufferPtr mRing; - - DEF_NEWDEL(DSoundCapture) -}; - -DSoundCapture::~DSoundCapture() -{ - if(mDSCbuffer) - { - mDSCbuffer->Stop(); - mDSCbuffer->Release(); - mDSCbuffer = nullptr; - } - - if(mDSC) - mDSC->Release(); - mDSC = nullptr; -} - - -ALCenum DSoundCapture::open(const ALCchar *name) -{ - HRESULT hr; - if(CaptureDevices.empty()) - { - /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); - } - - const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) - { - name = CaptureDevices[0].name.c_str(); - guid = &CaptureDevices[0].guid; - } - else - { - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - guid = &iter->guid; - } - - switch(mDevice->FmtType) - { - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_ENUM; - - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - } - - WAVEFORMATEXTENSIBLE InputType{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - InputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtStereo: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtAmbi3D: - WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_ENUM; - } - - InputType.Format.wFormatTag = WAVE_FORMAT_PCM; - InputType.Format.nChannels = mDevice->channelsFromFmt(); - InputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8; - InputType.Format.nSamplesPerSec = mDevice->Frequency; - InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign; - InputType.Format.cbSize = 0; - InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; - if(mDevice->FmtType == DevFmtFloat) - InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - } - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - - DSCBUFFERDESC DSCBDescription{}; - DSCBDescription.dwSize = sizeof(DSCBDescription); - DSCBDescription.dwFlags = 0; - DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign; - DSCBDescription.lpwfxFormat = &InputType.Format; - - //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); - if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); - if(SUCCEEDED(hr)) - { - mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false); - if(!mRing) hr = DSERR_OUTOFMEMORY; - } - - if(FAILED(hr)) - { - ERR("Device init failed: 0x%08lx\n", hr); - - mRing = nullptr; - if(mDSCbuffer) - mDSCbuffer->Release(); - mDSCbuffer = nullptr; - if(mDSC) - mDSC->Release(); - mDSC = nullptr; - - return ALC_INVALID_VALUE; - } - - mBufferBytes = DSCBDescription.dwBufferBytes; - SetDefaultWFXChannelOrder(mDevice); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean DSoundCapture::start() -{ - HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; - if(FAILED(hr)) - { - ERR("start failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void DSoundCapture::stop() -{ - HRESULT hr{mDSCbuffer->Stop()}; - if(FAILED(hr)) - { - ERR("stop failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr); - } -} - -ALCenum DSoundCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint DSoundCapture::availableSamples() -{ - if(!mDevice->Connected.load(std::memory_order_acquire)) - return static_cast<ALCuint>(mRing->readSpace()); - - ALsizei FrameSize{mDevice->frameSizeFromFmt()}; - DWORD BufferBytes{mBufferBytes}; - DWORD LastCursor{mCursor}; - - DWORD ReadCursor; - void *ReadPtr1, *ReadPtr2; - DWORD ReadCnt1, ReadCnt2; - HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)}; - if(SUCCEEDED(hr)) - { - DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes}; - if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace()); - hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); - } - if(SUCCEEDED(hr)) - { - mRing->write(ReadPtr1, ReadCnt1/FrameSize); - if(ReadPtr2 != nullptr && ReadCnt2 > 0) - mRing->write(ReadPtr2, ReadCnt2/FrameSize); - hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); - mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes; - } - - if(FAILED(hr)) - { - ERR("update failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr); - } - - return static_cast<ALCuint>(mRing->readSpace()); -} - -} // namespace - - -BackendFactory &DSoundBackendFactory::getFactory() -{ - static DSoundBackendFactory factory{}; - return factory; -} - -bool DSoundBackendFactory::init() -{ -#ifdef HAVE_DYNLOAD - if(!ds_handle) - { - ds_handle = LoadLib("dsound.dll"); - if(!ds_handle) - { - ERR("Failed to load dsound.dll\n"); - return false; - } - -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \ - if(!p##f) \ - { \ - CloseLib(ds_handle); \ - ds_handle = nullptr; \ - return false; \ - } \ -} while(0) - LOAD_FUNC(DirectSoundCreate); - LOAD_FUNC(DirectSoundEnumerateW); - LOAD_FUNC(DirectSoundCaptureCreate); - LOAD_FUNC(DirectSoundCaptureEnumerateW); -#undef LOAD_FUNC - } -#endif - return true; -} - -bool DSoundBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - - /* Initialize COM to prevent name truncation */ - HRESULT hr; - 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 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; - } - if(SUCCEEDED(hrcom)) - CoUninitialize(); -} - -BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new DSoundPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new DSoundCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/dsound.h b/Alc/backends/dsound.h deleted file mode 100644 index 6bef0bfc..00000000 --- a/Alc/backends/dsound.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_DSOUND_H -#define BACKENDS_DSOUND_H - -#include "backends/base.h" - -struct DSoundBackendFactory 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_DSOUND_H */ diff --git a/Alc/backends/jack.cpp b/Alc/backends/jack.cpp deleted file mode 100644 index 3f81d08c..00000000 --- a/Alc/backends/jack.cpp +++ /dev/null @@ -1,562 +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 "backends/jack.h" - -#include <cstdlib> -#include <cstdio> -#include <memory.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#include <jack/jack.h> -#include <jack/ringbuffer.h> - - -namespace { - -constexpr ALCchar jackDevice[] = "JACK Default"; - - -#ifdef HAVE_DYNLOAD -#define JACK_FUNCS(MAGIC) \ - MAGIC(jack_client_open); \ - MAGIC(jack_client_close); \ - MAGIC(jack_client_name_size); \ - MAGIC(jack_get_client_name); \ - MAGIC(jack_connect); \ - MAGIC(jack_activate); \ - MAGIC(jack_deactivate); \ - MAGIC(jack_port_register); \ - MAGIC(jack_port_unregister); \ - MAGIC(jack_port_get_buffer); \ - MAGIC(jack_port_name); \ - MAGIC(jack_get_ports); \ - MAGIC(jack_free); \ - MAGIC(jack_get_sample_rate); \ - MAGIC(jack_set_error_function); \ - MAGIC(jack_set_process_callback); \ - MAGIC(jack_set_buffer_size_callback); \ - MAGIC(jack_set_buffer_size); \ - MAGIC(jack_get_buffer_size); - -void *jack_handle; -#define MAKE_FUNC(f) decltype(f) * p##f -JACK_FUNCS(MAKE_FUNC); -decltype(jack_error_callback) * pjack_error_callback; -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define jack_client_open pjack_client_open -#define jack_client_close pjack_client_close -#define jack_client_name_size pjack_client_name_size -#define jack_get_client_name pjack_get_client_name -#define jack_connect pjack_connect -#define jack_activate pjack_activate -#define jack_deactivate pjack_deactivate -#define jack_port_register pjack_port_register -#define jack_port_unregister pjack_port_unregister -#define jack_port_get_buffer pjack_port_get_buffer -#define jack_port_name pjack_port_name -#define jack_get_ports pjack_get_ports -#define jack_free pjack_free -#define jack_get_sample_rate pjack_get_sample_rate -#define jack_set_error_function pjack_set_error_function -#define jack_set_process_callback pjack_set_process_callback -#define jack_set_buffer_size_callback pjack_set_buffer_size_callback -#define jack_set_buffer_size pjack_set_buffer_size -#define jack_get_buffer_size pjack_get_buffer_size -#define jack_error_callback (*pjack_error_callback) -#endif -#endif - - -jack_options_t ClientOptions = JackNullOption; - -ALCboolean jack_load() -{ - ALCboolean error = ALC_FALSE; - -#ifdef HAVE_DYNLOAD - if(!jack_handle) - { - std::string missing_funcs; - -#ifdef _WIN32 -#define JACKLIB "libjack.dll" -#else -#define JACKLIB "libjack.so.0" -#endif - jack_handle = LoadLib(JACKLIB); - if(!jack_handle) - { - WARN("Failed to load %s\n", JACKLIB); - return ALC_FALSE; - } - - error = ALC_FALSE; -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \ - if(p##f == nullptr) { \ - error = ALC_TRUE; \ - missing_funcs += "\n" #f; \ - } \ -} while(0) - JACK_FUNCS(LOAD_FUNC); -#undef LOAD_FUNC - /* Optional symbols. These don't exist in all versions of JACK. */ -#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)) - LOAD_SYM(jack_error_callback); -#undef LOAD_SYM - - if(error) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(jack_handle); - jack_handle = nullptr; - } - } -#endif - - return !error; -} - - -struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~JackPlayback() override; - - static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg); - int bufferSizeNotify(jack_nframes_t numframes); - - static int processC(jack_nframes_t numframes, void *arg); - int process(jack_nframes_t numframes); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - - jack_client_t *mClient{nullptr}; - jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{}; - - RingBufferPtr mRing; - al::semaphore mSem; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(JackPlayback) -}; - -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); - jack_client_close(mClient); - mClient = nullptr; -} - - -int JackPlayback::bufferSizeNotifyC(jack_nframes_t numframes, void *arg) -{ return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); } - -int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) -{ - 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); - - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) - { - ERR("Failed to reallocate ringbuffer\n"); - aluHandleDisconnect(mDevice, "Failed to reallocate %u-sample buffer", bufsize); - } - return 0; -} - - -int JackPlayback::processC(jack_nframes_t numframes, void *arg) -{ return static_cast<JackPlayback*>(arg)->process(numframes); } - -int JackPlayback::process(jack_nframes_t numframes) -{ - jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS]; - ALsizei 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, data.first.len)}; - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat - { - ALfloat ret{*in}; - in += numchans; - return ret; - } - ); - data.first.buf += sizeof(ALfloat); - return outbuf + todo; - } - ); - jack_nframes_t total{todo}; - - todo = minu(numframes-total, data.second.len); - if(todo > 0) - { - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast<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(); - - 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; - } - ); - } - - return 0; -} - -int JackPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - if(mRing->writeSpace() < mDevice->UpdateSize) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - auto data = mRing->getWriteVector(); - auto todo = static_cast<ALuint>(data.first.len + data.second.len); - todo -= todo%mDevice->UpdateSize; - - ALuint len1{minu(data.first.len, todo)}; - ALuint len2{minu(data.second.len, todo-len1)}; - - aluMixData(mDevice, data.first.buf, len1); - if(len2 > 0) - aluMixData(mDevice, data.second.buf, len2); - mRing->writeAdvance(todo); - } - unlock(); - - return 0; -} - - -ALCenum JackPlayback::open(const ALCchar *name) -{ - if(!name) - name = jackDevice; - else if(strcmp(name, jackDevice) != 0) - return ALC_INVALID_VALUE; - - const char *client_name{"alsoft"}; - jack_status_t status; - mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); - if(mClient == nullptr) - { - ERR("jack_client_open() failed, status = 0x%02x\n", status); - return ALC_INVALID_VALUE; - } - if((status&JackServerStarted)) - TRACE("JACK server started\n"); - if((status&JackNameNotUnique)) - { - client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got `%s' instead\n", client_name); - } - - jack_set_process_callback(mClient, &JackPlayback::processC, this); - jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean 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); - - /* 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; - - /* Force 32-bit float output. */ - mDevice->FmtType = DevFmtFloat; - - ALsizei numchans{mDevice->channelsFromFmt()}; - auto ports_end = std::begin(mPort) + numchans; - auto bad_port = std::find_if_not(std::begin(mPort), ports_end, - [this](jack_port_t *&port) -> bool - { - std::string name{"channel_" + std::to_string(&port - mPort + 1)}; - port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - return port != nullptr; - } - ); - if(bad_port != ports_end) - { - ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == std::begin(mPort)) return ALC_FALSE; - - if(bad_port == std::begin(mPort)+1) - mDevice->FmtChans = DevFmtMono; - else - { - ports_end = mPort+2; - while(bad_port != ports_end) - { - jack_port_unregister(mClient, *(--bad_port)); - *bad_port = nullptr; - } - mDevice->FmtChans = DevFmtStereo; - } - numchans = std::distance(std::begin(mPort), bad_port); - } - - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) - { - ERR("Failed to allocate ringbuffer\n"); - return ALC_FALSE; - } - - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean JackPlayback::start() -{ - if(jack_activate(mClient)) - { - ERR("Failed to activate client\n"); - return ALC_FALSE; - } - - const char **ports{jack_get_ports(mClient, nullptr, nullptr, - JackPortIsPhysical|JackPortIsInput)}; - if(ports == nullptr) - { - ERR("No physical playback ports found\n"); - jack_deactivate(mClient); - return ALC_FALSE; - } - std::mismatch(std::begin(mPort), std::end(mPort), ports, - [this](const jack_port_t *port, const char *pname) -> bool - { - if(!port) return false; - if(!pname) - { - ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); - return false; - } - 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; - } - ); - jack_free(ports); - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - jack_deactivate(mClient); - return ALC_FALSE; -} - -void JackPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); - - jack_deactivate(mClient); -} - - -ClockLatency JackPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace()}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -void jack_msg_handler(const char *message) -{ - WARN("%s\n", message); -} - -} // namespace - -bool JackBackendFactory::init() -{ - if(!jack_load()) - return false; - - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) - ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer); - - 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_set_error_function(old_error_cb); - if(!client) - { - WARN("jack_client_open() failed, 0x%02x\n", status); - if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) - ERR("Unable to connect to JACK server\n"); - return false; - } - - jack_client_close(client); - return true; -} - -bool JackBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback); } - -void JackBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(jackDevice, sizeof(jackDevice)); - break; - - case DevProbe::Capture: - break; - } -} - -BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new JackPlayback{device}}; - return nullptr; -} - -BackendFactory &JackBackendFactory::getFactory() -{ - static JackBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/jack.h b/Alc/backends/jack.h deleted file mode 100644 index 10beebfb..00000000 --- a/Alc/backends/jack.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_JACK_H -#define BACKENDS_JACK_H - -#include "backends/base.h" - -struct JackBackendFactory 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_JACK_H */ diff --git a/Alc/backends/loopback.cpp b/Alc/backends/loopback.cpp deleted file mode 100644 index 4a1c641a..00000000 --- a/Alc/backends/loopback.cpp +++ /dev/null @@ -1,80 +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 "backends/loopback.h" - -#include "alcmain.h" -#include "alu.h" - - -namespace { - -struct LoopbackBackend final : public BackendBase { - LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { } - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - DEF_NEWDEL(LoopbackBackend) -}; - - -ALCenum LoopbackBackend::open(const ALCchar *name) -{ - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean LoopbackBackend::reset() -{ - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean LoopbackBackend::start() -{ return ALC_TRUE; } - -void LoopbackBackend::stop() -{ } - -} // namespace - - -bool LoopbackBackendFactory::init() -{ return true; } - -bool LoopbackBackendFactory::querySupport(BackendType) -{ return true; } - -void LoopbackBackendFactory::probe(DevProbe, std::string*) -{ } - -BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType) -{ return BackendPtr{new LoopbackBackend{device}}; } - -BackendFactory &LoopbackBackendFactory::getFactory() -{ - static LoopbackBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/loopback.h b/Alc/backends/loopback.h deleted file mode 100644 index 09c085b8..00000000 --- a/Alc/backends/loopback.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_LOOPBACK_H -#define BACKENDS_LOOPBACK_H - -#include "backends/base.h" - -struct LoopbackBackendFactory 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_LOOPBACK_H */ diff --git a/Alc/backends/null.cpp b/Alc/backends/null.cpp deleted file mode 100644 index ae58cb8b..00000000 --- a/Alc/backends/null.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/** - * 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 "backends/null.h" - -#include <exception> -#include <atomic> -#include <chrono> -#include <cstdint> -#include <cstring> -#include <functional> -#include <thread> - -#include "alcmain.h" -#include "almalloc.h" -#include "alu.h" -#include "logging.h" -#include "threads.h" - - -namespace { - -using std::chrono::seconds; -using std::chrono::milliseconds; -using std::chrono::nanoseconds; - -constexpr ALCchar nullDevice[] = "No Output"; - - -struct NullBackend final : public BackendBase { - NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(NullBackend) -}; - -int NullBackend::mixerProc() -{ - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - 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)) - { - auto now = std::chrono::steady_clock::now(); - - /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) - { - std::this_thread::sleep_for(restTime); - continue; - } - while(avail-done >= mDevice->UpdateSize) - { - lock(); - aluMixData(mDevice, nullptr, mDevice->UpdateSize); - unlock(); - done += mDevice->UpdateSize; - } - - /* For every completed second, increment the start time and reduce the - * samples done. This prevents the difference between the start time - * and current time from growing too large, while maintaining the - * correct number of samples to render. - */ - if(done >= mDevice->Frequency) - { - seconds s{done/mDevice->Frequency}; - start += s; - done -= mDevice->Frequency*s.count(); - } - } - - return 0; -} - - -ALCenum NullBackend::open(const ALCchar *name) -{ - if(!name) - name = nullDevice; - else if(strcmp(name, nullDevice) != 0) - return ALC_INVALID_VALUE; - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean NullBackend::reset() -{ - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean NullBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void NullBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); -} - -} // namespace - - -bool NullBackendFactory::init() -{ return true; } - -bool NullBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback); } - -void NullBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(nullDevice, sizeof(nullDevice)); - break; - case DevProbe::Capture: - break; - } -} - -BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new NullBackend{device}}; - return nullptr; -} - -BackendFactory &NullBackendFactory::getFactory() -{ - static NullBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/null.h b/Alc/backends/null.h deleted file mode 100644 index f19d5b4d..00000000 --- a/Alc/backends/null.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_NULL_H -#define BACKENDS_NULL_H - -#include "backends/base.h" - -struct NullBackendFactory 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_NULL_H */ diff --git a/Alc/backends/opensl.cpp b/Alc/backends/opensl.cpp deleted file mode 100644 index b34dc0cb..00000000 --- a/Alc/backends/opensl.cpp +++ /dev/null @@ -1,936 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* This is an OpenAL backend for Android using the native audio APIs based on - * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app - * bundled with NDK. - */ - -#include "config.h" - -#include "backends/opensl.h" - -#include <stdlib.h> -#include <jni.h> - -#include <new> -#include <array> -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#include <SLES/OpenSLES.h> -#include <SLES/OpenSLES_Android.h> -#include <SLES/OpenSLES_AndroidConfiguration.h> - - -namespace { - -/* Helper macros */ -#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) -#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS -#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS - - -constexpr ALCchar opensl_device[] = "OpenSL"; - - -SLuint32 GetChannelMask(DevFmtChannels chans) -{ - switch(chans) - { - case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; - case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; - case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_CENTER| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtAmbi3D: - break; - } - return 0; -} - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX -SLuint32 GetTypeRepresentation(DevFmtType type) -{ - switch(type) - { - case DevFmtUByte: - case DevFmtUShort: - case DevFmtUInt: - return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT; - case DevFmtByte: - case DevFmtShort: - case DevFmtInt: - return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - case DevFmtFloat: - return SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } - return 0; -} -#endif - -const char *res_str(SLresult result) -{ - switch(result) - { - case SL_RESULT_SUCCESS: return "Success"; - case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; - case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; - case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; - case SL_RESULT_RESOURCE_ERROR: return "Resource error"; - case SL_RESULT_RESOURCE_LOST: return "Resource lost"; - case SL_RESULT_IO_ERROR: return "I/O error"; - case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; - case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; - case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; - case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; - case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; - case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; - case SL_RESULT_INTERNAL_ERROR: return "Internal error"; - case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; - case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; - case SL_RESULT_CONTROL_LOST: return "Control lost"; -#ifdef SL_RESULT_READONLY - case SL_RESULT_READONLY: return "ReadOnly"; -#endif -#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED - case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; -#endif -#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE - case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; -#endif - } - 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) - - -struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~OpenSLPlayback() override; - - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - - /* engine interfaces */ - SLObjectItf mEngineObj{nullptr}; - SLEngineItf mEngine{nullptr}; - - /* output mix interfaces */ - SLObjectItf mOutputMix{nullptr}; - - /* buffer queue player interfaces */ - SLObjectItf mBufferQueueObj{nullptr}; - - RingBufferPtr mRing{nullptr}; - al::semaphore mSem; - - ALsizei mFrameSize{0}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OpenSLPlayback) -}; - -OpenSLPlayback::~OpenSLPlayback() -{ - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - if(mOutputMix) - VCALL0(mOutputMix,Destroy)(); - mOutputMix = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; -} - - -/* this callback handler is called every time a buffer finishes playing */ -void OpenSLPlayback::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast<OpenSLPlayback*>(context)->process(bq); } - -void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) -{ - /* A note on the ringbuffer usage: The buffer queue seems to hold on to the - * pointer passed to the Enqueue method, rather than copying the audio. - * Consequently, the ringbuffer contains the audio that is currently queued - * and waiting to play. This process() callback is called when a buffer is - * finished, so we simply move the read pointer up to indicate the space is - * available for writing again, and wake up the mixer thread to mix and - * queue more audio. - */ - mRing->readAdvance(1); - - mSem.post(); -} - -int OpenSLPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - SLPlayItf player; - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bufferQueue)}; - 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"); - } - - lock(); - if(SL_RESULT_SUCCESS != result) - aluHandleDisconnect(mDevice, "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)) - { - if(mRing->writeSpace() == 0) - { - SLuint32 state{0}; - - result = VCALL(player,GetPlayState)(&state); - PRINTERR(result, "player->GetPlayState"); - if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING) - { - result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING); - PRINTERR(result, "player->SetPlayState"); - } - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result); - break; - } - - if(mRing->writeSpace() == 0) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - } - - auto data = mRing->getWriteVector(); - aluMixData(mDevice, data.first.buf, data.first.len*mDevice->UpdateSize); - if(data.second.len > 0) - aluMixData(mDevice, data.second.buf, data.second.len*mDevice->UpdateSize); - - size_t todo{data.first.len + data.second.len}; - mRing->writeAdvance(todo); - - for(size_t i{0};i < todo;i++) - { - if(!data.first.len) - { - data.first = data.second; - data.second.buf = nullptr; - data.second.len = 0; - } - - result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); - PRINTERR(result, "bufferQueue->Enqueue"); - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result); - break; - } - - data.first.len--; - data.first.buf += mDevice->UpdateSize*mFrameSize; - } - } - unlock(); - - return 0; -} - - -ALCenum OpenSLPlayback::open(const ALCchar *name) -{ - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; - - // create engine - SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr); - PRINTERR(result, "engine->CreateOutputMix"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "outputMix->Realize"); - } - - if(SL_RESULT_SUCCESS != result) - { - if(mOutputMix) - VCALL0(mOutputMix,Destroy)(); - mOutputMix = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; - - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OpenSLPlayback::reset() -{ - SLDataLocator_AndroidSimpleBufferQueue loc_bufq; - SLDataLocator_OutputMix loc_outmix; - SLDataSource audioSrc; - SLDataSink audioSnk; - SLresult result; - - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - mRing = nullptr; - -#if 0 - if(!mDevice->Flags.get<FrequencyRequest>()) - { - /* FIXME: Disabled until I figure out how to get the Context needed for - * the getSystemService call. - */ - JNIEnv *env = Android_GetJNIEnv(); - jobject jctx = Android_GetContext(); - - /* Get necessary stuff for using java.lang.Integer, - * android.content.Context, and android.media.AudioManager. - */ - jclass int_cls = JCALL(env,FindClass)("java/lang/Integer"); - jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls, - "parseInt", "(Ljava/lang/String;)I" - ); - TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint); - - jclass ctx_cls = JCALL(env,FindClass)("android/content/Context"); - jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls, - "AUDIO_SERVICE", "Ljava/lang/String;" - ); - jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls, - "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" - ); - TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n", - ctx_cls, ctx_audsvc, ctx_getSysSvc); - - jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager"); - jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls, - "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;" - ); - jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls, - "getProperty", "(Ljava/lang/String;)Ljava/lang/String;" - ); - TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n", - audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty); - - const char *strchars; - jstring strobj; - - /* Now make the calls. */ - //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE); - strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc); - jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); - strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate); - jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //int sampleRate = Integer.parseInt(srateStr); - sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr); - - strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr); - TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars); - JCALL(env,ReleaseStringUTFChars)(srateStr, strchars); - - if(!sampleRate) sampleRate = device->Frequency; - else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE); - } -#endif - - mDevice->FmtChans = DevFmtStereo; - mDevice->FmtType = DevFmtShort; - - SetDefaultWFXChannelOrder(mDevice); - mFrameSize = mDevice->frameSizeFromFmt(); - - - const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; - const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; - - loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX - SLAndroidDataFormat_PCM_EX format_pcm{}; - format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; -#endif - - audioSrc.pLocator = &loc_bufq; - audioSrc.pFormat = &format_pcm; - - loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = mOutputMix; - audioSnk.pLocator = &loc_outmix; - audioSnk.pFormat = nullptr; - - - result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), - ids.data(), reqs.data()); - 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"); - 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"); - } - - /* Clear any error since this was optional. */ - result = SL_RESULT_SUCCESS; - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "bufferQueue->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - try { - mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true); - } - catch(std::exception& e) { - ERR("Failed allocating ring buffer %ux%ux%u: %s\n", mDevice->UpdateSize, - num_updates, mFrameSize, e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } - } - - if(SL_RESULT_SUCCESS != result) - { - if(mBufferQueueObj) - VCALL0(mBufferQueueObj,Destroy)(); - mBufferQueueObj = nullptr; - - return ALC_FALSE; - } - - return ALC_TRUE; -} - -ALCboolean OpenSLPlayback::start() -{ - mRing->reset(); - - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS != result) - return ALC_FALSE; - - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - if(SL_RESULT_SUCCESS != result) return ALC_FALSE; - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OpenSLPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); - - SLPlayItf player; - SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)}; - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); - PRINTERR(result, "player->SetPlayState"); - } - - SLAndroidSimpleBufferQueueItf bufferQueue; - result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "bufferQueue->GetInterface"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL0(bufferQueue,Clear)(); - PRINTERR(result, "bufferQueue->Clear"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr); - PRINTERR(result, "bufferQueue->RegisterCallback"); - } - if(SL_RESULT_SUCCESS == result) - { - SLAndroidSimpleBufferQueueState state; - do { - std::this_thread::yield(); - result = VCALL(bufferQueue,GetState)(&state); - } while(SL_RESULT_SUCCESS == result && state.count > 0); - PRINTERR(result, "bufferQueue->GetState"); - } -} - -ClockLatency OpenSLPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct OpenSLCapture final : public BackendBase { - OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~OpenSLCapture() override; - - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - /* engine interfaces */ - SLObjectItf mEngineObj{nullptr}; - SLEngineItf mEngine; - - /* recording interfaces */ - SLObjectItf mRecordObj{nullptr}; - - RingBufferPtr mRing{nullptr}; - ALCuint mSplOffset{0u}; - - ALsizei mFrameSize{0}; - - DEF_NEWDEL(OpenSLCapture) -}; - -OpenSLCapture::~OpenSLCapture() -{ - if(mRecordObj) - VCALL0(mRecordObj,Destroy)(); - mRecordObj = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; -} - - -void OpenSLCapture::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast<OpenSLCapture*>(context)->process(bq); } - -void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) -{ - /* A new chunk has been written into the ring buffer, advance it. */ - mRing->writeAdvance(1); -} - - -ALCenum OpenSLCapture::open(const ALCchar* name) -{ - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; - - SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - mFrameSize = mDevice->frameSizeFromFmt(); - /* Ensure the total length is at least 100ms */ - ALsizei length{maxi(mDevice->BufferSize, mDevice->Frequency/10)}; - /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - ALsizei update_len{clampi(mDevice->BufferSize/3, mDevice->Frequency/100, - mDevice->Frequency/100*5)}; - ALsizei num_updates{(length+update_len-1) / update_len}; - - try { - mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false); - - mDevice->UpdateSize = update_len; - mDevice->BufferSize = mRing->writeSpace() * update_len; - } - catch(std::exception& e) { - ERR("Failed to allocate ring buffer: %s\n", e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } - } - if(SL_RESULT_SUCCESS == result) - { - const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; - const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; - - SLDataLocator_IODevice loc_dev{}; - loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; - loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT; - loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; - loc_dev.device = nullptr; - - SLDataSource audioSrc{}; - audioSrc.pLocator = &loc_dev; - audioSrc.pFormat = nullptr; - - SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; - loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; - -#ifdef SL_ANDROID_DATAFORMAT_PCM_EX - SLAndroidDataFormat_PCM_EX format_pcm{}; - format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; -#endif - - SLDataSink audioSnk{}; - audioSnk.pLocator = &loc_bq; - audioSnk.pFormat = &format_pcm; - - result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, - ids.size(), ids.data(), reqs.data()); - PRINTERR(result, "engine->CreateAudioRecorder"); - } - if(SL_RESULT_SUCCESS == result) - { - /* 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"); - 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"); - } - - /* Clear any error since this was optional. */ - result = SL_RESULT_SUCCESS; - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "recordObj->Realize"); - } - - SLAndroidSimpleBufferQueueItf bufferQueue; - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); - } - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - } - if(SL_RESULT_SUCCESS == result) - { - const ALuint chunk_size{mDevice->UpdateSize * mFrameSize}; - - auto data = mRing->getWriteVector(); - 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"); - } - 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"); - } - } - - if(SL_RESULT_SUCCESS != result) - { - if(mRecordObj) - VCALL0(mRecordObj,Destroy)(); - mRecordObj = nullptr; - - if(mEngineObj) - VCALL0(mEngineObj,Destroy)(); - mEngineObj = nullptr; - mEngine = nullptr; - - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OpenSLCapture::start() -{ - SLRecordItf record; - SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); - - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); - PRINTERR(result, "record->SetRecordState"); - } - - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result); - return ALC_FALSE; - } - - return ALC_TRUE; -} - -void OpenSLCapture::stop() -{ - SLRecordItf record; - SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); - - if(SL_RESULT_SUCCESS == result) - { - result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED); - PRINTERR(result, "record->SetRecordState"); - } -} - -ALCenum OpenSLCapture::captureSamples(void* buffer, ALCuint samples) -{ - ALsizei chunk_size = mDevice->UpdateSize * mFrameSize; - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result; - ALCuint i; - - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); - - /* Read the desired samples from the ring buffer then advance its read - * pointer. - */ - auto data = mRing->getReadVector(); - for(i = 0;i < samples;) - { - ALCuint rem{minu(samples - i, mDevice->UpdateSize - mSplOffset)}; - memcpy((ALCbyte*)buffer + i*mFrameSize, data.first.buf + mSplOffset*mFrameSize, - rem * mFrameSize); - - mSplOffset += rem; - if(mSplOffset == mDevice->UpdateSize) - { - /* Finished a chunk, reset the offset and advance the read pointer. */ - mSplOffset = 0; - - mRing->readAdvance(1); - result = VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); - if(SL_RESULT_SUCCESS != result) break; - - data.first.len--; - if(!data.first.len) - data.first = data.second; - else - data.first.buf += chunk_size; - } - - i += rem; - } - - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", result); - return ALC_INVALID_DEVICE; - } - - return ALC_NO_ERROR; -} - -ALCuint OpenSLCapture::availableSamples() -{ return mRing->readSpace()*mDevice->UpdateSize - mSplOffset; } - -} // namespace - -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) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(opensl_device, sizeof(opensl_device)); - break; - } -} - -BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new OpenSLPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new OpenSLCapture{device}}; - return nullptr; -} - -BackendFactory &OSLBackendFactory::getFactory() -{ - static OSLBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/opensl.h b/Alc/backends/opensl.h deleted file mode 100644 index 809aa339..00000000 --- a/Alc/backends/opensl.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_OSL_H -#define BACKENDS_OSL_H - -#include "backends/base.h" - -struct OSLBackendFactory 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_OSL_H */ diff --git a/Alc/backends/oss.cpp b/Alc/backends/oss.cpp deleted file mode 100644 index 8cfe9e96..00000000 --- a/Alc/backends/oss.cpp +++ /dev/null @@ -1,751 +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 "backends/oss.h" - -#include <fcntl.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <cerrno> -#include <cstdio> -#include <cstring> -#include <exception> -#include <functional> -#include <memory> -#include <new> -#include <string> -#include <thread> -#include <utility> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alu.h" -#include "logging.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" - -#include <sys/soundcard.h> - -/* - * The OSS documentation talks about SOUND_MIXER_READ, but the header - * only contains MIXER_READ. Play safe. Same for WRITE. - */ -#ifndef SOUND_MIXER_READ -#define SOUND_MIXER_READ MIXER_READ -#endif -#ifndef SOUND_MIXER_WRITE -#define SOUND_MIXER_WRITE MIXER_WRITE -#endif - -#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000) -#define ALC_OSS_COMPAT -#endif -#ifndef SNDCTL_AUDIOINFO -#define ALC_OSS_COMPAT -#endif - -/* - * FreeBSD strongly discourages the use of specific devices, - * such as those returned in oss_audioinfo.devnode - */ -#ifdef __FreeBSD__ -#define ALC_OSS_DEVNODE_TRUC -#endif - -namespace { - -constexpr char DefaultName[] = "OSS Default"; -std::string DefaultPlayback{"/dev/dsp"}; -std::string DefaultCapture{"/dev/dsp"}; - -struct DevMap { - std::string name; - 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; - - -#ifdef ALC_OSS_COMPAT - -#define DSP_CAP_OUTPUT 0x00020000 -#define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector<DevMap> *devlist, int type) -{ - 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) -{ -#ifdef ALC_OSS_DEVNODE_TRUC - for(size_t i{0};i < plen;i++) - { - if(path[i] == '.') - { - if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0) - hlen = hlen + i - plen; - plen = i; - } - } -#endif - if(handle[0] == '\0') - { - 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()); - - auto iter = std::find_if(list->cbegin(), list->cend(), - [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; } - ); - if(iter != list->cend()) - return; - - int count{1}; - std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - - 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) -{ - int fd{open("/dev/mixer", O_RDONLY)}; - if(fd < 0) - { - TRACE("Could not open /dev/mixer: %s\n", strerror(errno)); - goto done; - } - - oss_sysinfo si; - if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1) - { - TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno)); - goto done; - } - - for(int i{0};i < si.numaudios;i++) - { - oss_audioinfo ai; - ai.dev = i; - if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1) - { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno)); - continue; - } - if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') - continue; - - const char *handle; - size_t len; - if(ai.handle[0] != '\0') - { - len = strnlen(ai.handle, sizeof(ai.handle)); - handle = ai.handle; - } - else - { - len = strnlen(ai.name, sizeof(ai.name)); - handle = ai.name; - } - - ALCossListAppend(devlist, handle, len, ai.devnode, - strnlen(ai.devnode, sizeof(ai.devnode))); - } - -done: - if(fd >= 0) - close(fd); - fd = -1; - - const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; - auto iter = std::find_if(devlist->cbegin(), devlist->cend(), - [defdev](const DevMap &entry) -> bool - { return entry.device_name == defdev; } - ); - if(iter == devlist->cend()) - devlist->insert(devlist->begin(), DevMap{DefaultName, defdev}); - else - { - DevMap entry{std::move(*iter)}; - devlist->erase(iter); - devlist->insert(devlist->begin(), std::move(entry)); - } - devlist->shrink_to_fit(); -} - -#endif - -int log2i(ALCuint x) -{ - int y = 0; - while (x > 1) - { - x >>= 1; - y++; - } - return y; -} - - -struct OSSPlayback final : public BackendBase { - OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~OSSPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - int mFd{-1}; - - al::vector<ALubyte> mMixData; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OSSPlayback) -}; - -OSSPlayback::~OSSPlayback() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - - -int OSSPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLOUT; - - unlock(); - int pret{poll(&pollitem, 1, 1000)}; - lock(); - if(pret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - else if(pret == 0) - { - WARN("poll timeout\n"); - continue; - } - - ALubyte *write_ptr{mMixData.data()}; - size_t to_write{mMixData.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) - { - ssize_t wrote{write(mFd, write_ptr, to_write)}; - if(wrote < 0) - { - if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) - continue; - ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed writing playback samples: %s", - strerror(errno)); - break; - } - - to_write -= wrote; - write_ptr += wrote; - } - } - unlock(); - - return 0; -} - - -ALCenum OSSPlayback::open(const ALCchar *name) -{ - const char *devname{DefaultPlayback.c_str()}; - if(!name) - name = DefaultName; - else - { - if(PlaybackDevices.empty()) - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; - devname = iter->device_name.c_str(); - } - - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OSSPlayback::reset() -{ - int numFragmentsLogSize; - int log2FragmentSize; - unsigned int periods; - audio_buf_info info; - ALuint frameSize; - int numChannels; - int ossFormat; - int ossSpeed; - const char *err; - - switch(mDevice->FmtType) - { - case DevFmtByte: - ossFormat = AFMT_S8; - break; - case DevFmtUByte: - ossFormat = AFMT_U8; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - ossFormat = AFMT_S16_NE; - break; - } - - periods = mDevice->BufferSize / mDevice->UpdateSize; - numChannels = mDevice->channelsFromFmt(); - ossSpeed = mDevice->Frequency; - frameSize = numChannels * mDevice->bytesFromFmt(); - /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - log2FragmentSize = maxi(log2i(mDevice->UpdateSize*frameSize), 4); - numFragmentsLogSize = (periods << 16) | log2FragmentSize; - -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} - /* Don't fail if SETFRAGMENT fails. We can handle just about anything - * that's reported back via GETOSPACE */ - ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - return ALC_FALSE; - } -#undef CHECKERR - - if(mDevice->channelsFromFmt() != numChannels) - { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - numChannels); - return ALC_FALSE; - } - - if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || - (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || - (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), - ossFormat); - return ALC_FALSE; - } - - mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = info.fragsize / frameSize; - mDevice->BufferSize = info.fragments * mDevice->UpdateSize; - - SetDefaultChannelOrder(mDevice); - - mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - - return ALC_TRUE; -} - -ALCboolean OSSPlayback::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OSSPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); -} - - -struct OSScapture final : public BackendBase { - OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~OSScapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - int mFd{-1}; - - RingBufferPtr mRing{nullptr}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(OSScapture) -}; - -OSScapture::~OSScapture() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - - -int OSScapture::recordProc() -{ - SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - while(!mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLIN; - - int sret{poll(&pollitem, 1, 1000)}; - if(sret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno)); - break; - } - else if(sret == 0) - { - WARN("poll timeout\n"); - continue; - } - - auto vec = mRing->getWriteVector(); - if(vec.first.len > 0) - { - ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; - if(amt < 0) - { - ERR("read failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed reading capture samples: %s", - strerror(errno)); - break; - } - mRing->writeAdvance(amt/frame_size); - } - } - - return 0; -} - - -ALCenum OSScapture::open(const ALCchar *name) -{ - const char *devname{DefaultCapture.c_str()}; - if(!name) - name = DefaultName; - else - { - if(CaptureDevices.empty()) - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ); - if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; - devname = iter->device_name.c_str(); - } - - mFd = ::open(devname, O_RDONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - int ossFormat{}; - switch(mDevice->FmtType) - { - case DevFmtByte: - ossFormat = AFMT_S8; - break; - case DevFmtUByte: - ossFormat = AFMT_U8; - break; - case DevFmtShort: - ossFormat = AFMT_S16_NE; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - - int periods{4}; - int numChannels{mDevice->channelsFromFmt()}; - int frameSize{numChannels * mDevice->bytesFromFmt()}; - int ossSpeed{static_cast<int>(mDevice->Frequency)}; - int log2FragmentSize{log2i(mDevice->BufferSize * frameSize / periods)}; - - /* according to the OSS spec, 16 bytes are the minimum */ - log2FragmentSize = std::max(log2FragmentSize, 4); - int numFragmentsLogSize{(periods << 16) | log2FragmentSize}; - - audio_buf_info info; - const char *err; -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); - CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } -#undef CHECKERR - - if(mDevice->channelsFromFmt() != numChannels) - { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - numChannels); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } - - if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || - (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || - (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ossFormat); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } - - mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false); - if(!mRing) - { - ERR("Ring buffer create failed\n"); - close(mFd); - mFd = -1; - return ALC_OUT_OF_MEMORY; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean OSScapture::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void OSScapture::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); -} - -ALCenum OSScapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint OSScapture::availableSamples() -{ return mRing->readSpace(); } - -} // namespace - - -BackendFactory &OSSBackendFactory::getFactory() -{ - static OSSBackendFactory factory{}; - return factory; -} - -bool OSSBackendFactory::init() -{ - if(auto devopt = ConfigValueStr(nullptr, "oss", "device")) - DefaultPlayback = std::move(*devopt); - if(auto capopt = ConfigValueStr(nullptr, "oss", "capture")) - DefaultCapture = std::move(*capopt); - - return true; -} - -bool OSSBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void OSSBackendFactory::probe(DevProbe type, 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); - } - }; - - switch(type) - { - case DevProbe::Playback: - PlaybackDevices.clear(); - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices.clear(); - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new OSSPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new OSScapture{device}}; - return nullptr; -} diff --git a/Alc/backends/oss.h b/Alc/backends/oss.h deleted file mode 100644 index 9e63d7b6..00000000 --- a/Alc/backends/oss.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_OSS_H -#define BACKENDS_OSS_H - -#include "backends/base.h" - -struct OSSBackendFactory 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_OSS_H */ diff --git a/Alc/backends/portaudio.cpp b/Alc/backends/portaudio.cpp deleted file mode 100644 index 73e972c5..00000000 --- a/Alc/backends/portaudio.cpp +++ /dev/null @@ -1,463 +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 "backends/portaudio.h" - -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "ringbuffer.h" -#include "compat.h" - -#include <portaudio.h> - - -namespace { - -constexpr ALCchar pa_device[] = "PortAudio Default"; - - -#ifdef HAVE_DYNLOAD -void *pa_handle; -#define MAKE_FUNC(x) decltype(x) * p##x -MAKE_FUNC(Pa_Initialize); -MAKE_FUNC(Pa_Terminate); -MAKE_FUNC(Pa_GetErrorText); -MAKE_FUNC(Pa_StartStream); -MAKE_FUNC(Pa_StopStream); -MAKE_FUNC(Pa_OpenStream); -MAKE_FUNC(Pa_CloseStream); -MAKE_FUNC(Pa_GetDefaultOutputDevice); -MAKE_FUNC(Pa_GetDefaultInputDevice); -MAKE_FUNC(Pa_GetStreamInfo); -#undef MAKE_FUNC - -#ifndef IN_IDE_PARSER -#define Pa_Initialize pPa_Initialize -#define Pa_Terminate pPa_Terminate -#define Pa_GetErrorText pPa_GetErrorText -#define Pa_StartStream pPa_StartStream -#define Pa_StopStream pPa_StopStream -#define Pa_OpenStream pPa_OpenStream -#define Pa_CloseStream pPa_CloseStream -#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice -#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice -#define Pa_GetStreamInfo pPa_GetStreamInfo -#endif -#endif - - -struct PortPlayback final : public BackendBase { - PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~PortPlayback() override; - - static int writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - PaStream *mStream{nullptr}; - PaStreamParameters mParams{}; - ALuint mUpdateSize{0u}; - - DEF_NEWDEL(PortPlayback) -}; - -PortPlayback::~PortPlayback() -{ - PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; - if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); - mStream = nullptr; -} - - -int PortPlayback::writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) -{ - return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortPlayback::writeCallback(const void*, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, - const PaStreamCallbackFlags) -{ - lock(); - aluMixData(mDevice, outputBuffer, framesPerBuffer); - unlock(); - return 0; -} - - -ALCenum PortPlayback::open(const ALCchar *name) -{ - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; - - mUpdateSize = mDevice->UpdateSize; - - 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; - - mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - - switch(mDevice->FmtType) - { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtUShort: - /* fall-through */ - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtUInt: - /* fall-through */ - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; - } - -retry_open: - PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize, - paNoFlag, &PortPlayback::writeCallbackC, this)}; - if(err != paNoError) - { - if(mParams.sampleFormat == paFloat32) - { - mParams.sampleFormat = paInt16; - goto retry_open; - } - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; - -} - -ALCboolean PortPlayback::reset() -{ - const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = streamInfo->sampleRate; - mDevice->UpdateSize = mUpdateSize; - - if(mParams.sampleFormat == paInt8) - mDevice->FmtType = DevFmtByte; - else if(mParams.sampleFormat == paUInt8) - mDevice->FmtType = DevFmtUByte; - else if(mParams.sampleFormat == paInt16) - mDevice->FmtType = DevFmtShort; - else if(mParams.sampleFormat == paInt32) - mDevice->FmtType = DevFmtInt; - else if(mParams.sampleFormat == paFloat32) - mDevice->FmtType = DevFmtFloat; - else - { - ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat); - return ALC_FALSE; - } - - if(mParams.channelCount == 2) - mDevice->FmtChans = DevFmtStereo; - else if(mParams.channelCount == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unexpected channel count: %u\n", mParams.channelCount); - return ALC_FALSE; - } - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; -} - -ALCboolean PortPlayback::start() -{ - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void PortPlayback::stop() -{ - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); -} - - -struct PortCapture final : public BackendBase { - PortCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~PortCapture() override; - - static int readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - PaStream *mStream{nullptr}; - PaStreamParameters mParams; - - RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(PortCapture) -}; - -PortCapture::~PortCapture() -{ - PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; - if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); - mStream = nullptr; -} - - -int PortCapture::readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void* userData) -{ - return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortCapture::readCallback(const void *inputBuffer, void*, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, - const PaStreamCallbackFlags) -{ - mRing->write(inputBuffer, framesPerBuffer); - return 0; -} - - -ALCenum PortCapture::open(const ALCchar *name) -{ - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - ALsizei frame_size{mDevice->frameSizeFromFmt()}; - - mRing = CreateRingBuffer(samples, frame_size, false); - if(!mRing) return ALC_INVALID_VALUE; - - auto devidopt = ConfigValueInt(nullptr, "port", "capture"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = 0.0f; - mParams.hostApiSpecificStreamInfo = nullptr; - - switch(mDevice->FmtType) - { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; - case DevFmtUInt: - case DevFmtUShort: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - mParams.channelCount = mDevice->channelsFromFmt(); - - PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, - paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; - if(err != paNoError) - { - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - - -ALCboolean PortCapture::start() -{ - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; -} - -void PortCapture::stop() -{ - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); -} - - -ALCuint PortCapture::availableSamples() -{ return mRing->readSpace(); } - -ALCenum PortCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -} // namespace - - -bool PortBackendFactory::init() -{ - PaError err; - -#ifdef HAVE_DYNLOAD - if(!pa_handle) - { -#ifdef _WIN32 -# define PALIB "portaudio.dll" -#elif defined(__APPLE__) && defined(__MACH__) -# define PALIB "libportaudio.2.dylib" -#elif defined(__OpenBSD__) -# define PALIB "libportaudio.so" -#else -# define PALIB "libportaudio.so.2" -#endif - - pa_handle = LoadLib(PALIB); - if(!pa_handle) - return false; - -#define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \ - if(p##f == nullptr) \ - { \ - CloseLib(pa_handle); \ - pa_handle = nullptr; \ - return false; \ - } \ -} while(0) - LOAD_FUNC(Pa_Initialize); - LOAD_FUNC(Pa_Terminate); - LOAD_FUNC(Pa_GetErrorText); - LOAD_FUNC(Pa_StartStream); - LOAD_FUNC(Pa_StopStream); - LOAD_FUNC(Pa_OpenStream); - LOAD_FUNC(Pa_CloseStream); - LOAD_FUNC(Pa_GetDefaultOutputDevice); - LOAD_FUNC(Pa_GetDefaultInputDevice); - LOAD_FUNC(Pa_GetStreamInfo); -#undef LOAD_FUNC - - if((err=Pa_Initialize()) != paNoError) - { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); - CloseLib(pa_handle); - pa_handle = nullptr; - return false; - } - } -#else - if((err=Pa_Initialize()) != paNoError) - { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); - return false; - } -#endif - return true; -} - -bool PortBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void PortBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(pa_device, sizeof(pa_device)); - break; - } -} - -BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PortPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new PortCapture{device}}; - return nullptr; -} - -BackendFactory &PortBackendFactory::getFactory() -{ - static PortBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/portaudio.h b/Alc/backends/portaudio.h deleted file mode 100644 index 082e9020..00000000 --- a/Alc/backends/portaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_PORTAUDIO_H -#define BACKENDS_PORTAUDIO_H - -#include "backends/base.h" - -struct PortBackendFactory 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_PORTAUDIO_H */ diff --git a/Alc/backends/pulseaudio.cpp b/Alc/backends/pulseaudio.cpp deleted file mode 100644 index da209c8d..00000000 --- a/Alc/backends/pulseaudio.cpp +++ /dev/null @@ -1,1532 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2009 by Konstantinos Natsakis <[email protected]> - * Copyright (C) 2010 by Chris Robinson <[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 "backends/pulseaudio.h" - -#include <poll.h> -#include <cstring> - -#include <array> -#include <string> -#include <vector> -#include <atomic> -#include <thread> -#include <algorithm> -#include <condition_variable> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" -#include "alexcpt.h" - -#include <pulse/pulseaudio.h> - - -namespace { - -#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_get_api); \ - MAGIC(pa_context_new); \ - MAGIC(pa_context_unref); \ - MAGIC(pa_context_get_state); \ - MAGIC(pa_context_disconnect); \ - MAGIC(pa_context_set_state_callback); \ - MAGIC(pa_context_errno); \ - MAGIC(pa_context_connect); \ - MAGIC(pa_context_get_server_info); \ - MAGIC(pa_context_get_sink_info_by_name); \ - MAGIC(pa_context_get_sink_info_list); \ - MAGIC(pa_context_get_source_info_by_name); \ - MAGIC(pa_context_get_source_info_list); \ - MAGIC(pa_stream_new); \ - MAGIC(pa_stream_unref); \ - MAGIC(pa_stream_drop); \ - MAGIC(pa_stream_get_state); \ - MAGIC(pa_stream_peek); \ - MAGIC(pa_stream_write); \ - MAGIC(pa_stream_connect_record); \ - MAGIC(pa_stream_connect_playback); \ - MAGIC(pa_stream_readable_size); \ - MAGIC(pa_stream_writable_size); \ - MAGIC(pa_stream_is_corked); \ - MAGIC(pa_stream_cork); \ - MAGIC(pa_stream_is_suspended); \ - MAGIC(pa_stream_get_device_name); \ - MAGIC(pa_stream_get_latency); \ - MAGIC(pa_stream_set_write_callback); \ - MAGIC(pa_stream_set_buffer_attr); \ - MAGIC(pa_stream_get_buffer_attr); \ - MAGIC(pa_stream_get_sample_spec); \ - MAGIC(pa_stream_get_time); \ - MAGIC(pa_stream_set_read_callback); \ - MAGIC(pa_stream_set_state_callback); \ - MAGIC(pa_stream_set_moved_callback); \ - MAGIC(pa_stream_set_underflow_callback); \ - MAGIC(pa_stream_new_with_proplist); \ - MAGIC(pa_stream_disconnect); \ - MAGIC(pa_stream_set_buffer_attr_callback); \ - MAGIC(pa_stream_begin_write); \ - 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_operation_get_state); \ - MAGIC(pa_operation_unref); \ - MAGIC(pa_sample_spec_valid); \ - MAGIC(pa_frame_size); \ - MAGIC(pa_strerror); \ - MAGIC(pa_path_get_filename); \ - MAGIC(pa_get_binary_name); \ - MAGIC(pa_xmalloc); \ - MAGIC(pa_xfree); - -void *pulse_handle; -#define MAKE_FUNC(x) decltype(x) * p##x -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_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 -#define pa_context_disconnect ppa_context_disconnect -#define pa_context_set_state_callback ppa_context_set_state_callback -#define pa_context_errno ppa_context_errno -#define pa_context_connect ppa_context_connect -#define pa_context_get_server_info ppa_context_get_server_info -#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name -#define pa_context_get_sink_info_list ppa_context_get_sink_info_list -#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name -#define pa_context_get_source_info_list ppa_context_get_source_info_list -#define pa_stream_new ppa_stream_new -#define pa_stream_unref ppa_stream_unref -#define pa_stream_disconnect ppa_stream_disconnect -#define pa_stream_drop ppa_stream_drop -#define pa_stream_set_write_callback ppa_stream_set_write_callback -#define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr -#define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr -#define pa_stream_get_sample_spec ppa_stream_get_sample_spec -#define pa_stream_get_time ppa_stream_get_time -#define pa_stream_set_read_callback ppa_stream_set_read_callback -#define pa_stream_set_state_callback ppa_stream_set_state_callback -#define pa_stream_set_moved_callback ppa_stream_set_moved_callback -#define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback -#define pa_stream_connect_record ppa_stream_connect_record -#define pa_stream_connect_playback ppa_stream_connect_playback -#define pa_stream_readable_size ppa_stream_readable_size -#define pa_stream_writable_size ppa_stream_writable_size -#define pa_stream_is_corked ppa_stream_is_corked -#define pa_stream_cork ppa_stream_cork -#define pa_stream_is_suspended ppa_stream_is_suspended -#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_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_operation_get_state ppa_operation_get_state -#define pa_operation_unref ppa_operation_unref -#define pa_sample_spec_valid ppa_sample_spec_valid -#define pa_frame_size ppa_frame_size -#define pa_strerror ppa_strerror -#define pa_stream_get_state ppa_stream_get_state -#define pa_stream_peek ppa_stream_peek -#define pa_stream_write ppa_stream_write -#define pa_xfree ppa_xfree -#define pa_path_get_filename ppa_path_get_filename -#define pa_get_binary_name ppa_get_binary_name -#define pa_xmalloc ppa_xmalloc -#endif /* IN_IDE_PARSER */ - -#endif - - -constexpr pa_channel_map MonoChanMap{ - 1, {PA_CHANNEL_POSITION_MONO} -}, StereoChanMap{ - 2, {PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT} -}, QuadChanMap{ - 4, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT - } -}, X51ChanMap{ - 6, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT - } -}, X51RearChanMap{ - 6, { - 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 - } -}, X61ChanMap{ - 7, { - PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_REAR_CENTER, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT - } -}, X71ChanMap{ - 8, { - 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 - } -}; - -size_t ChannelFromPulse(pa_channel_position_t chan) -{ - switch(chan) - { - case PA_CHANNEL_POSITION_INVALID: break; - case PA_CHANNEL_POSITION_MONO: return FrontCenter; - case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft; - case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight; - case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter; - case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter; - case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft; - case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight; - case PA_CHANNEL_POSITION_LFE: return LFE; - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break; - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break; - case PA_CHANNEL_POSITION_SIDE_LEFT: return SideLeft; - case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight; - case PA_CHANNEL_POSITION_AUX0: return Aux0; - case PA_CHANNEL_POSITION_AUX1: return Aux1; - case PA_CHANNEL_POSITION_AUX2: return Aux2; - case PA_CHANNEL_POSITION_AUX3: return Aux3; - case PA_CHANNEL_POSITION_AUX4: return Aux4; - case PA_CHANNEL_POSITION_AUX5: return Aux5; - case PA_CHANNEL_POSITION_AUX6: return Aux6; - case PA_CHANNEL_POSITION_AUX7: return Aux7; - case PA_CHANNEL_POSITION_AUX8: return Aux8; - case PA_CHANNEL_POSITION_AUX9: return Aux9; - case PA_CHANNEL_POSITION_AUX10: return Aux10; - case PA_CHANNEL_POSITION_AUX11: return Aux11; - case PA_CHANNEL_POSITION_AUX12: return Aux12; - case PA_CHANNEL_POSITION_AUX13: return Aux13; - case PA_CHANNEL_POSITION_AUX14: return Aux14; - case PA_CHANNEL_POSITION_AUX15: return Aux15; - case PA_CHANNEL_POSITION_AUX16: break; - case PA_CHANNEL_POSITION_AUX17: break; - case PA_CHANNEL_POSITION_AUX18: break; - case PA_CHANNEL_POSITION_AUX19: break; - case PA_CHANNEL_POSITION_AUX20: break; - case PA_CHANNEL_POSITION_AUX21: break; - case PA_CHANNEL_POSITION_AUX22: break; - case PA_CHANNEL_POSITION_AUX23: break; - case PA_CHANNEL_POSITION_AUX24: break; - case PA_CHANNEL_POSITION_AUX25: break; - case PA_CHANNEL_POSITION_AUX26: break; - case PA_CHANNEL_POSITION_AUX27: break; - case PA_CHANNEL_POSITION_AUX28: break; - case PA_CHANNEL_POSITION_AUX29: break; - case PA_CHANNEL_POSITION_AUX30: break; - case PA_CHANNEL_POSITION_AUX31: break; - case PA_CHANNEL_POSITION_TOP_CENTER: break; - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft; - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight; - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break; - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft; - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight; - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break; - case PA_CHANNEL_POSITION_MAX: break; - } - throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan}; -} - -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) -{ - device->RealOut.ChannelIndex.fill(-1); - for(int i{0};i < chanmap.channels;++i) - device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = i; -} - - -/* *grumble* Don't use enums for bitflags. */ -inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) -{ return pa_stream_flags_t(int(lhs) | int(rhs)); } -inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) -{ - lhs = pa_stream_flags_t(int(lhs) | int(rhs)); - return lhs; -} -inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) -{ - lhs = pa_context_flags_t(int(lhs) | int(rhs)); - return lhs; -} - -inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) -{ - lhs = pa_stream_flags_t(int(lhs) & rhs); - return lhs; -} - - -/* Global flags and properties */ -pa_context_flags_t pulse_ctx_flags; - -pa_mainloop *pulse_mainloop{nullptr}; - -std::mutex pulse_lock; -std::condition_variable pulse_condvar; - -int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) -{ - auto plock = static_cast<std::unique_lock<std::mutex>*>(userdata); - plock->unlock(); - int r{poll(ufds, nfds, timeout)}; - plock->lock(); - return r; -} - -int pulse_mainloop_thread() -{ - SetRTPriority(); - - std::unique_lock<std::mutex> plock{pulse_lock}; - pulse_mainloop = pa_mainloop_new(); - - pa_mainloop_set_poll_func(pulse_mainloop, pulse_poll_func, &plock); - pulse_condvar.notify_all(); - - int ret{}; - pa_mainloop_run(pulse_mainloop, &ret); - - pa_mainloop_free(pulse_mainloop); - pulse_mainloop = nullptr; - - return ret; -} - - -/* PulseAudio Event Callbacks */ -void context_state_callback(pa_context *context, void* /*pdata*/) -{ - pa_context_state_t state{pa_context_get_state(context)}; - if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) - pulse_condvar.notify_all(); -} - -void stream_state_callback(pa_stream *stream, void* /*pdata*/) -{ - pa_stream_state_t state{pa_stream_get_state(stream)}; - if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) - pulse_condvar.notify_all(); -} - -void stream_success_callback(pa_stream* /*stream*/, int /*success*/, void* /*pdata*/) -{ - pulse_condvar.notify_all(); -} - -void wait_for_operation(pa_operation *op, std::unique_lock<std::mutex> &plock) -{ - if(op) - { - while(pa_operation_get_state(op) == PA_OPERATION_RUNNING) - pulse_condvar.wait(plock); - pa_operation_unref(op); - } -} - - -pa_context *connect_context(std::unique_lock<std::mutex> &plock) -{ - const char *name{"OpenAL Soft"}; - - const PathNamePair &binname = GetProcBinary(); - if(!binname.fname.empty()) - name = binname.fname.c_str(); - - if(UNLIKELY(!pulse_mainloop)) - { - std::thread{pulse_mainloop_thread}.detach(); - while(!pulse_mainloop) - pulse_condvar.wait(plock); - } - - pa_context *context{pa_context_new(pa_mainloop_get_api(pulse_mainloop), name)}; - if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_context_new() failed"}; - - pa_context_set_state_callback(context, context_state_callback, nullptr); - - int err; - if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) - { - pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) - { - if(!PA_CONTEXT_IS_GOOD(state)) - { - err = pa_context_errno(context); - if(err > 0) err = -err; - break; - } - - pulse_condvar.wait(plock); - } - } - pa_context_set_state_callback(context, nullptr, nullptr); - - if(err < 0) - { - pa_context_unref(context); - throw al::backend_exception{ALC_INVALID_VALUE, "Context did not connect (%s)", - pa_strerror(err)}; - } - - return context; -} - - -void pulse_close(pa_context *context, pa_stream *stream) -{ - std::lock_guard<std::mutex> _{pulse_lock}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } - - pa_context_disconnect(context); - pa_context_unref(context); -} - - -struct DevMap { - std::string name; - std::string device_name; -}; - -bool checkName(const al::vector<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; - - -pa_stream *pulse_connect_stream(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) -{ - 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)", - pa_strerror(pa_context_errno(context))}; - - pa_stream_set_state_callback(stream, stream_state_callback, nullptr); - - int err{(type==BackendType::Playback) ? - pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : - pa_stream_connect_record(stream, device_name, attr, flags)}; - if(err < 0) - { - pa_stream_unref(stream); - throw al::backend_exception{ALC_INVALID_VALUE, "%s did not connect (%s)", stream_id, - pa_strerror(err)}; - } - - pa_stream_state_t state; - while((state=pa_stream_get_state(stream)) != PA_STREAM_READY) - { - if(!PA_STREAM_IS_GOOD(state)) - { - int 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)}; - } - - pulse_condvar.wait(plock); - } - pa_stream_set_state_callback(stream, nullptr, nullptr); - - return stream; -} - - -void device_sink_callback(pa_context*, const pa_sink_info *info, int eol, void*) -{ - if(eol) - { - pulse_condvar.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() -{ - PlaybackDevices.clear(); - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_context *context{connect_context(plock)}; - - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, - nullptr, BackendType::Playback)}; - pa_operation *op{pa_context_get_sink_info_by_name(context, - pa_stream_get_device_name(stream), device_sink_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_sink_info_list(context, device_sink_callback, nullptr); - wait_for_operation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - -void device_source_callback(pa_context*, const pa_source_info *info, int eol, void*) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != CaptureDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} - -void probeCaptureDevices() -{ - CaptureDevices.clear(); - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_context *context{connect_context(plock)}; - - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 1; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, nullptr, - BackendType::Capture)}; - pa_operation *op{pa_context_get_source_info_by_name(context, - pa_stream_get_device_name(stream), device_source_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_source_info_list(context, device_source_callback, nullptr); - wait_for_operation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - -struct PulsePlayback final : public BackendBase { - PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~PulsePlayback() override; - - static void bufferAttrCallbackC(pa_stream *stream, void *pdata); - void bufferAttrCallback(pa_stream *stream); - - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); - - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); - - static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata); - void streamWriteCallback(pa_stream *stream, size_t nbytes); - - static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol); - - static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol); - - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - - std::string mDeviceName; - - pa_buffer_attr mAttr; - pa_sample_spec mSpec; - - pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; - - ALuint mFrameSize{0u}; - - DEF_NEWDEL(PulsePlayback) -}; - -PulsePlayback::~PulsePlayback() -{ - if(!mContext) - return; - - pulse_close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} - - -void PulsePlayback::bufferAttrCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); } - -void PulsePlayback::bufferAttrCallback(pa_stream *stream) -{ - /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new - * buffer attributes? Changing UpdateSize will change the ALC_REFRESH - * property, which probably shouldn't change between device resets. But - * leaving it alone means ALC_REFRESH will be off. - */ - mAttr = *(pa_stream_get_buffer_attr(stream)); - TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); -} - -void PulsePlayback::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->contextStateCallback(context); } - -void PulsePlayback::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Playback state failure"); - } - pulse_condvar.notify_all(); -} - -void PulsePlayback::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); } - -void PulsePlayback::streamStateCallback(pa_stream *stream) -{ - if(pa_stream_get_state(stream) == PA_STREAM_FAILED) - { - ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Playback stream failure"); - } - pulse_condvar.notify_all(); -} - -void PulsePlayback::streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); } - -void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) -{ - void *buf{pa_xmalloc(nbytes)}; - aluMixData(mDevice, buf, nbytes/mFrameSize); - - 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)); -} - -void PulsePlayback::sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); } - -void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) -{ - struct ChannelMap { - DevFmtChannels chans; - pa_channel_map map; - }; - static constexpr std::array<ChannelMap,7> chanmaps{{ - { DevFmtX71, X71ChanMap }, - { DevFmtX61, X61ChanMap }, - { DevFmtX51, X51ChanMap }, - { DevFmtX51Rear, X51RearChanMap }, - { DevFmtQuad, QuadChanMap }, - { DevFmtStereo, StereoChanMap }, - { DevFmtMono, MonoChanMap } - }}; - - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - auto chanmap = std::find_if(chanmaps.cbegin(), chanmaps.cend(), - [info](const ChannelMap &chanmap) -> bool - { return pa_channel_map_superset(&info->channel_map, &chanmap.map); } - ); - if(chanmap != chanmaps.cend()) - { - if(!mDevice->Flags.get<ChannelsRequest>()) - mDevice->FmtChans = chanmap->chans; - } - else - { - 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); - } - - 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); -} - -void PulsePlayback::sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); } - -void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - mDevice->DeviceName = info->description; -} - -void PulsePlayback::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); } - -void PulsePlayback::streamMovedCallback(pa_stream *stream) -{ - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); -} - - -ALCenum PulsePlayback::open(const ALCchar *name) -{ - const char *pulse_name{nullptr}; - const char *dev_name{nullptr}; - - if(name) - { - if(PlaybackDevices.empty()) - probePlaybackDevices(); - - 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}; - pulse_name = iter->device_name.c_str(); - dev_name = iter->name.c_str(); - } - - std::unique_lock<std::mutex> plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulsePlayback::contextStateCallbackC, this); - - pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) - flags |= PA_STREAM_DONT_MOVE; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - if(!pulse_name) - { - pulse_name = getenv("ALSOFT_PULSE_DEFAULT"); - if(pulse_name && !pulse_name[0]) pulse_name = nullptr; - } - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr, - BackendType::Playback); - - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - mFrameSize = pa_frame_size(pa_stream_get_sample_spec(mStream)); - - mDeviceName = pa_stream_get_device_name(mStream); - if(!dev_name) - { - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkNameCallbackC, this)}; - wait_for_operation(op, plock); - } - else - mDevice->DeviceName = dev_name; - - return ALC_NO_ERROR; -} - -ALCboolean PulsePlayback::reset() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - if(mStream) - { - pa_stream_set_state_callback(mStream, nullptr, nullptr); - pa_stream_set_moved_callback(mStream, nullptr, nullptr); - pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); - pa_stream_disconnect(mStream); - pa_stream_unref(mStream); - mStream = nullptr; - } - - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkInfoCallbackC, this)}; - wait_for_operation(op, plock); - - 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)) - flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0)) - { - /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some - * reason. So if the user wants to adjust the overall device latency, - * we can't ask to get write signals as soon as minreq is reached. - */ - flags &= ~PA_STREAM_EARLY_REQUESTS; - flags |= PA_STREAM_ADJUST_LATENCY; - } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) || - !mDevice->Flags.get<FrequencyRequest>()) - flags |= PA_STREAM_FIX_RATE; - - pa_channel_map chanmap{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - } - SetChannelOrderFromMap(mDevice, chanmap); - - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; - } - mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); - if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample spec"}; - - mAttr.maxlength = -1; - mAttr.tlength = mDevice->BufferSize * pa_frame_size(&mSpec); - mAttr.prebuf = 0; - mAttr.minreq = mDevice->UpdateSize * pa_frame_size(&mSpec); - mAttr.fragsize = -1; - - mStream = pulse_connect_stream(mDeviceName.c_str(), plock, mContext, flags, &mAttr, &mSpec, - &chanmap, BackendType::Playback); - - pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - - mSpec = *(pa_stream_get_sample_spec(mStream)); - mFrameSize = pa_frame_size(&mSpec); - - if(mDevice->Frequency != mSpec.rate) - { - /* Server updated our playback rate, so modify the buffer attribs - * 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))}; - - mAttr.maxlength = -1; - mAttr.tlength = buflen * mFrameSize; - mAttr.prebuf = 0; - mAttr.minreq = perlen * mFrameSize; - - op = pa_stream_set_buffer_attr(mStream, &mAttr, stream_success_callback, nullptr); - wait_for_operation(op, plock); - - mDevice->Frequency = mSpec.rate; - } - - pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this); - bufferAttrCallback(mStream); - - mDevice->BufferSize = mAttr.tlength / mFrameSize; - mDevice->UpdateSize = mAttr.minreq / mFrameSize; - - /* HACK: prebuf should be 0 as that's what we set it to. However on some - * systems it comes back as non-0, so we have to make sure the device will - * write enough audio to start playback. The lack of manual start control - * may have unintended consequences, but it's better than not starting at - * all. - */ - if(mAttr.prebuf != 0) - { - ALuint len{mAttr.prebuf / mFrameSize}; - if(len <= mDevice->BufferSize) - ERR("Non-0 prebuf, %u samples (%u bytes), device has %u samples\n", - len, mAttr.prebuf, mDevice->BufferSize); - } - - return ALC_TRUE; -} - -ALCboolean PulsePlayback::start() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); - - return ALC_TRUE; -} - -void PulsePlayback::stop() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - - pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); -} - - -ClockLatency PulsePlayback::getClockLatency() -{ - ClockLatency ret; - pa_usec_t latency; - int neg, err; - - { std::lock_guard<std::mutex> _{pulse_lock}; - ret.ClockTime = GetDeviceClockTime(mDevice); - err = pa_stream_get_latency(mStream, &latency, &neg); - } - - if(UNLIKELY(err != 0)) - { - /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon - * after starting the stream and no timing info has been received from - * the server yet. Should we wait, possibly stalling the app, or give a - * dummy value? Either way, it shouldn't be 0. */ - if(err != -PA_ERR_NODATA) - ERR("Failed to get stream latency: 0x%x\n", err); - latency = 0; - neg = 0; - } - else if(UNLIKELY(neg)) - latency = 0; - ret.Latency = std::chrono::microseconds{latency}; - - return ret; -} - - -void PulsePlayback::lock() -{ pulse_lock.lock(); } - -void PulsePlayback::unlock() -{ pulse_lock.unlock(); } - - -struct PulseCapture final : public BackendBase { - PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~PulseCapture() override; - - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); - - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); - - static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata); - void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol); - - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - - std::string mDeviceName; - - ALCuint mLastReadable{0u}; - al::byte mSilentVal{}; - - al::span<const al::byte> mCapBuffer; - ssize_t mCapLen{0}; - - pa_buffer_attr mAttr{}; - pa_sample_spec mSpec{}; - - pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; - - DEF_NEWDEL(PulseCapture) -}; - -PulseCapture::~PulseCapture() -{ - if(!mContext) - return; - - pulse_close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} - -void PulseCapture::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast<PulseCapture*>(pdata)->contextStateCallback(context); } - -void PulseCapture::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Capture state failure"); - } - pulse_condvar.notify_all(); -} - -void PulseCapture::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); } - -void PulseCapture::streamStateCallback(pa_stream *stream) -{ - if(pa_stream_get_state(stream) == PA_STREAM_FAILED) - { - ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Capture stream failure"); - } - pulse_condvar.notify_all(); -} - -void PulseCapture::sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) -{ static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); } - -void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) -{ - if(eol) - { - pulse_condvar.notify_all(); - return; - } - mDevice->DeviceName = info->description; -} - -void PulseCapture::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); } - -void PulseCapture::streamMovedCallback(pa_stream *stream) -{ - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); -} - - -ALCenum PulseCapture::open(const ALCchar *name) -{ - const char *pulse_name{nullptr}; - if(name) - { - if(CaptureDevices.empty()) - probeCaptureDevices(); - - 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}; - pulse_name = iter->device_name.c_str(); - mDevice->DeviceName = iter->name; - } - - std::unique_lock<std::mutex> plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulseCapture::contextStateCallbackC, this); - - pa_channel_map chanmap{}; - switch(mDevice->FmtChans) - { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtChannelsString(mDevice->FmtChans)}; - } - SetChannelOrderFromMap(mDevice, chanmap); - - switch(mDevice->FmtType) - { - case DevFmtUByte: - mSilentVal = al::byte(0x80); - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; - } - mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); - if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample format"}; - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - - mAttr.minreq = -1; - mAttr.prebuf = -1; - mAttr.maxlength = samples * pa_frame_size(&mSpec); - mAttr.tlength = -1; - mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * pa_frame_size(&mSpec); - - pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) - flags |= PA_STREAM_DONT_MOVE; - - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, - BackendType::Capture); - - pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); - pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); - - mDeviceName = pa_stream_get_device_name(mStream); - if(mDevice->DeviceName.empty()) - { - pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(), - &PulseCapture::sourceNameCallbackC, this)}; - wait_for_operation(op, plock); - } - - return ALC_NO_ERROR; -} - -ALCboolean PulseCapture::start() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); - return ALC_TRUE; -} - -void PulseCapture::stop() -{ - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); -} - -ALCenum PulseCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - al::span<al::byte> dstbuf{static_cast<al::byte*>(buffer), samples * pa_frame_size(&mSpec)}; - - /* Capture is done in fragment-sized chunks, so we loop until we get all - * that's available */ - mLastReadable -= dstbuf.size(); - std::lock_guard<std::mutex> _{pulse_lock}; - while(!dstbuf.empty()) - { - if(mCapBuffer.empty()) - { - if(UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire))) - break; - const pa_stream_state_t state{pa_stream_get_state(mStream)}; - if(UNLIKELY(!PA_STREAM_IS_GOOD(state))) - { - aluHandleDisconnect(mDevice, "Bad capture state: %u", state); - break; - } - const void *capbuf; - size_t caplen; - if(UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0)) - { - aluHandleDisconnect(mDevice, "Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); - break; - } - if(caplen == 0) break; - if(UNLIKELY(!capbuf)) - mCapLen = -static_cast<ssize_t>(caplen); - else - mCapLen = static_cast<ssize_t>(caplen); - mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen}; - } - - const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; - if(UNLIKELY(mCapLen < 0)) - std::fill_n(dstbuf.begin(), rem, mSilentVal); - else - std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); - dstbuf = dstbuf.subspan(rem); - mCapBuffer = mCapBuffer.subspan(rem); - - if(mCapBuffer.empty()) - { - pa_stream_drop(mStream); - mCapLen = 0; - } - } - if(!dstbuf.empty()) - std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); - - return ALC_NO_ERROR; -} - -ALCuint PulseCapture::availableSamples() -{ - size_t readable{mCapBuffer.size()}; - - if(mDevice->Connected.load(std::memory_order_acquire)) - { - std::lock_guard<std::mutex> _{pulse_lock}; - size_t got{pa_stream_readable_size(mStream)}; - if(static_cast<ssize_t>(got) < 0) - { - ERR("pa_stream_readable_size() failed: %s\n", pa_strerror(got)); - aluHandleDisconnect(mDevice, "Failed getting readable size: %s", pa_strerror(got)); - } - else - { - const auto caplen = static_cast<size_t>(std::abs(mCapLen)); - if(got > caplen) readable += got - caplen; - } - } - - readable = std::min<size_t>(readable, std::numeric_limits<ALCuint>::max()); - mLastReadable = std::max(mLastReadable, static_cast<ALCuint>(readable)); - return mLastReadable / pa_frame_size(&mSpec); -} - - -ClockLatency PulseCapture::getClockLatency() -{ - ClockLatency ret; - pa_usec_t latency; - int neg, err; - - { std::lock_guard<std::mutex> _{pulse_lock}; - ret.ClockTime = GetDeviceClockTime(mDevice); - err = pa_stream_get_latency(mStream, &latency, &neg); - } - - if(UNLIKELY(err != 0)) - { - ERR("Failed to get stream latency: 0x%x\n", err); - latency = 0; - neg = 0; - } - else if(UNLIKELY(neg)) - latency = 0; - ret.Latency = std::chrono::microseconds{latency}; - - return ret; -} - - -void PulseCapture::lock() -{ pulse_lock.lock(); } - -void PulseCapture::unlock() -{ pulse_lock.unlock(); } - -} // namespace - - -bool PulseBackendFactory::init() -{ -#ifdef HAVE_DYNLOAD - if(!pulse_handle) - { - bool ret{true}; - std::string missing_funcs; - -#ifdef _WIN32 -#define PALIB "libpulse-0.dll" -#elif defined(__APPLE__) && defined(__MACH__) -#define PALIB "libpulse.0.dylib" -#else -#define PALIB "libpulse.so.0" -#endif - pulse_handle = LoadLib(PALIB); - if(!pulse_handle) - { - WARN("Failed to load %s\n", PALIB); - return false; - } - -#define LOAD_FUNC(x) do { \ - p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x)); \ - if(!(p##x)) { \ - ret = false; \ - missing_funcs += "\n" #x; \ - } \ -} while(0) - PULSE_FUNCS(LOAD_FUNC) -#undef LOAD_FUNC - - if(!ret) - { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); - CloseLib(pulse_handle); - pulse_handle = nullptr; - return false; - } - } -#endif /* HAVE_DYNLOAD */ - - pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1)) - pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; - - try { - std::unique_lock<std::mutex> plock{pulse_lock}; - pa_context *context{connect_context(plock)}; - pa_context_disconnect(context); - pa_context_unref(context); - return true; - } - catch(...) { - return false; - } -} - -bool PulseBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void PulseBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - switch(type) - { - case 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; - } -} - -BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PulsePlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new PulseCapture{device}}; - return nullptr; -} - -BackendFactory &PulseBackendFactory::getFactory() -{ - static PulseBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/pulseaudio.h b/Alc/backends/pulseaudio.h deleted file mode 100644 index 40f3e305..00000000 --- a/Alc/backends/pulseaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_PULSEAUDIO_H -#define BACKENDS_PULSEAUDIO_H - -#include "backends/base.h" - -class PulseBackendFactory 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_PULSEAUDIO_H */ diff --git a/Alc/backends/qsa.cpp b/Alc/backends/qsa.cpp deleted file mode 100644 index 64ed53aa..00000000 --- a/Alc/backends/qsa.cpp +++ /dev/null @@ -1,953 +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 "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; - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::unique_ptr<qsa_data> mExtraData; - - DEF_NEWDEL(PlaybackWrapper) -}; - - -FORCE_ALIGN static int qsa_proc_playback(void *ptr) -{ - PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr); - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - sched_param param; - char* write_ptr; - ALint len; - int sret; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - /* Increase default 10 priority to 11 to avoid jerky sound */ - SchedGet(0, 0, ¶m); - param.sched_priority=param.sched_curpriority+1; - SchedSet(0, 0, SCHED_NOCHANGE, ¶m); - - const ALint frame_size = device->frameSizeFromFmt(); - - self->lock(); - while(!data->mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - self->unlock(); - sret = poll(&pollitem, 1, 2000); - self->lock(); - if(sret == -1) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll error: %s\n", strerror(errno)); - aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - if(sret == 0) - { - ERR("poll timeout\n"); - continue; - } - - len = data->size; - write_ptr = static_cast<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; - } - } - } - self->unlock(); - - return 0; -} - -/************/ -/* Playback */ -/************/ - -static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int status; - - std::unique_ptr<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); -} - -ALCenum PlaybackWrapper::open(const ALCchar *name) -{ return qsa_open_playback(this, name); } - -ALCboolean PlaybackWrapper::reset() -{ return qsa_reset_playback(this); } - -ALCboolean PlaybackWrapper::start() -{ return qsa_start_playback(this); } - -void PlaybackWrapper::stop() -{ qsa_stop_playback(this); } - - -/***********/ -/* Capture */ -/***********/ - -struct CaptureWrapper final : public BackendBase { - CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~CaptureWrapper() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::unique_ptr<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); -} - -ALCenum CaptureWrapper::open(const ALCchar *name) -{ return qsa_open_capture(this, name); } - -ALCboolean CaptureWrapper::start() -{ qsa_start_capture(this); return ALC_TRUE; } - -void CaptureWrapper::stop() -{ qsa_stop_capture(this); } - -ALCenum CaptureWrapper::captureSamples(void *buffer, ALCuint samples) -{ return qsa_capture_samples(this, buffer, samples); } - -ALCuint CaptureWrapper::availableSamples() -{ return qsa_available_samples(this); } - -} // namespace - - -bool QSABackendFactory::init() -{ return true; } - -bool QSABackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void QSABackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - const char *n = entry.name; - if(n && n[0]) - outnames->append(n, strlen(n)+1); - }; - - switch (type) - { - case DevProbe::Playback: - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device); - break; - case DevProbe::Capture: - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device); - break; - } -} - -BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PlaybackWrapper{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CaptureWrapper{device}}; - return nullptr; -} - -BackendFactory &QSABackendFactory::getFactory() -{ - static QSABackendFactory factory{}; - return factory; -} diff --git a/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 deleted file mode 100644 index 97547959..00000000 --- a/Alc/backends/sdl2.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2018 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/sdl2.h" - -#include <stdlib.h> -#include <SDL2/SDL.h> - -#include <string> - -#include "alcmain.h" -#include "alu.h" -#include "threads.h" -#include "compat.h" - - -namespace { - -#ifdef _WIN32 -#define DEVNAME_PREFIX "OpenAL Soft on " -#else -#define DEVNAME_PREFIX "" -#endif - -constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; - -struct Sdl2Backend final : public BackendBase { - Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { } - ~Sdl2Backend() override; - - static void audioCallbackC(void *ptr, Uint8 *stream, int len); - void audioCallback(Uint8 *stream, int len); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - void lock() override; - void unlock() override; - - SDL_AudioDeviceID mDeviceID{0u}; - ALsizei mFrameSize{0}; - - ALuint mFrequency{0u}; - DevFmtChannels mFmtChans{}; - DevFmtType mFmtType{}; - ALuint mUpdateSize{0u}; - - DEF_NEWDEL(Sdl2Backend) -}; - -Sdl2Backend::~Sdl2Backend() -{ - if(mDeviceID) - SDL_CloseAudioDevice(mDeviceID); - mDeviceID = 0; -} - -void Sdl2Backend::audioCallbackC(void *ptr, Uint8 *stream, int len) -{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); } - -void Sdl2Backend::audioCallback(Uint8 *stream, int len) -{ - assert((len % mFrameSize) == 0); - aluMixData(mDevice, stream, len / mFrameSize); -} - -ALCenum Sdl2Backend::open(const ALCchar *name) -{ - SDL_AudioSpec want{}, have{}; - want.freq = mDevice->Frequency; - switch(mDevice->FmtType) - { - case DevFmtUByte: want.format = AUDIO_U8; break; - case DevFmtByte: want.format = AUDIO_S8; break; - case DevFmtUShort: want.format = AUDIO_U16SYS; break; - case DevFmtShort: want.format = AUDIO_S16SYS; break; - case DevFmtUInt: /* fall-through */ - case DevFmtInt: want.format = AUDIO_S32SYS; break; - case DevFmtFloat: want.format = AUDIO_F32; break; - } - want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = mDevice->UpdateSize; - want.callback = &Sdl2Backend::audioCallbackC; - want.userdata = this; - - /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't - * necessarily the first in the list. - */ - if(!name || strcmp(name, defaultDeviceName) == 0) - mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - else - { - const size_t prefix_len = strlen(DEVNAME_PREFIX); - if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - else - mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - if(mDeviceID == 0) - return ALC_INVALID_VALUE; - - mDevice->Frequency = have.freq; - if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; - else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; - else - { - ERR("Got unhandled SDL channel count: %d\n", (int)have.channels); - return ALC_INVALID_VALUE; - } - switch(have.format) - { - case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; - case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; - case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; - case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; - case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; - case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; - default: - ERR("Got unsupported SDL format: 0x%04x\n", have.format); - return ALC_INVALID_VALUE; - } - 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; - - mDevice->DeviceName = name ? name : defaultDeviceName; - return ALC_NO_ERROR; -} - -ALCboolean Sdl2Backend::reset() -{ - mDevice->Frequency = mFrequency; - mDevice->FmtChans = mFmtChans; - mDevice->FmtType = mFmtType; - mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; -} - -ALCboolean Sdl2Backend::start() -{ - SDL_PauseAudioDevice(mDeviceID, 0); - return ALC_TRUE; -} - -void Sdl2Backend::stop() -{ SDL_PauseAudioDevice(mDeviceID, 1); } - -void Sdl2Backend::lock() -{ SDL_LockAudioDevice(mDeviceID); } - -void Sdl2Backend::unlock() -{ SDL_UnlockAudioDevice(mDeviceID); } - -} // namespace - -BackendFactory &SDL2BackendFactory::getFactory() -{ - static SDL2BackendFactory factory{}; - return factory; -} - -bool SDL2BackendFactory::init() -{ return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); } - -bool SDL2BackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void SDL2BackendFactory::probe(DevProbe type, std::string *outnames) -{ - if(type != DevProbe::Playback) - return; - - int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; - - /* Includes null char. */ - 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); - } -} - -BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new Sdl2Backend{device}}; - return nullptr; -} diff --git a/Alc/backends/sdl2.h b/Alc/backends/sdl2.h deleted file mode 100644 index 041d47ee..00000000 --- a/Alc/backends/sdl2.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SDL2_H -#define BACKENDS_SDL2_H - -#include "backends/base.h" - -struct SDL2BackendFactory 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_SDL2_H */ diff --git a/Alc/backends/sndio.cpp b/Alc/backends/sndio.cpp deleted file mode 100644 index 587f67bb..00000000 --- a/Alc/backends/sndio.cpp +++ /dev/null @@ -1,495 +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 "backends/sndio.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "threads.h" -#include "vector.h" -#include "ringbuffer.h" - -#include <sndio.h> - - -namespace { - -static const ALCchar sndio_device[] = "SndIO Default"; - - -struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~SndioPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - sio_hdl *mSndHandle{nullptr}; - - al::vector<ALubyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SndioPlayback) -}; - -SndioPlayback::~SndioPlayback() -{ - if(mSndHandle) - sio_close(mSndHandle); - mSndHandle = nullptr; -} - -int SndioPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto WritePtr = static_cast<ALubyte*>(mBuffer.data()); - size_t len{mBuffer.size()}; - - lock(); - aluMixData(mDevice, WritePtr, len/frameSize); - unlock(); - while(len > 0 && !mKillNow.load(std::memory_order_acquire)) - { - size_t wrote{sio_write(mSndHandle, WritePtr, len)}; - if(wrote == 0) - { - ERR("sio_write failed\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); - break; - } - - len -= wrote; - WritePtr += wrote; - } - } - - return 0; -} - - -ALCenum SndioPlayback::open(const ALCchar *name) -{ - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; - - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SndioPlayback::reset() -{ - sio_par par; - sio_initpar(&par); - - par.rate = mDevice->Frequency; - par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1); - - switch(mDevice->FmtType) - { - 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; - } - par.le = SIO_LE_NATIVE; - - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_FALSE; - } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_FALSE; - } - - 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; - else - { - ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); - return ALC_FALSE; - } - - SetDefaultChannelOrder(mDevice); - - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.bufsz + par.round; - - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); - - return ALC_TRUE; -} - -ALCboolean SndioPlayback::start() -{ - if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - sio_stop(mSndHandle); - return ALC_FALSE; -} - -void SndioPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); -} - - -struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~SndioCapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - sio_hdl *mSndHandle{nullptr}; - - RingBufferPtr mRing; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SndioCapture) -}; - -SndioCapture::~SndioCapture() -{ - if(mSndHandle) - sio_close(mSndHandle); - mSndHandle = nullptr; -} - -int SndioCapture::recordProc() -{ - SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); - - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - if(todo == 0) - { - static char junk[4096]; - sio_read(mSndHandle, junk, - minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); - continue; - } - - size_t total{0u}; - data.first.len *= frameSize; - data.second.len *= frameSize; - todo = minz(todo, mDevice->UpdateSize) * frameSize; - while(total < todo) - { - if(!data.first.len) - data.first = data.second; - - size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; - if(!got) - { - aluHandleDisconnect(mDevice, "Failed to read capture samples"); - break; - } - - data.first.buf += got; - data.first.len -= got; - total += got; - } - mRing->writeAdvance(total / frameSize); - } - - return 0; -} - - -ALCenum SndioCapture::open(const ALCchar *name) -{ - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; - - mSndHandle = sio_open(nullptr, SIO_REC, 0); - if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } - - sio_par par; - sio_initpar(&par); - - switch(mDevice->FmtType) - { - case DevFmtByte: - par.bps = 1; - par.sig = 1; - break; - case DevFmtUByte: - par.bps = 1; - par.sig = 0; - break; - case DevFmtShort: - par.bps = 2; - par.sig = 1; - break; - case DevFmtUShort: - par.bps = 2; - par.sig = 0; - break; - case DevFmtInt: - par.bps = 4; - par.sig = 1; - break; - case DevFmtUInt: - par.bps = 4; - par.sig = 0; - break; - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; - } - par.bits = par.bps * 8; - par.le = SIO_LE_NATIVE; - par.msb = SIO_LE_NATIVE ? 0 : 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; - - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_INVALID_VALUE; - } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_INVALID_VALUE; - } - - if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) || - (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) || - (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) || - (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) || - (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) || - (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) || - mDevice->channelsFromFmt() != (ALsizei)par.rchan || - mDevice->Frequency != par.rate) - { - ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n", - DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate); - return ALC_INVALID_VALUE; - } - - mRing = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false); - if(!mRing) - { - ERR("Failed to allocate %u-byte ringbuffer\n", mDevice->BufferSize*par.bps*par.rchan); - return ALC_OUT_OF_MEMORY; - } - - SetDefaultChannelOrder(mDevice); - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SndioCapture::start() -{ - if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { - } - sio_stop(mSndHandle); - return ALC_FALSE; -} - -void SndioCapture::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); -} - -ALCenum SndioCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint SndioCapture::availableSamples() -{ return mRing->readSpace(); } - -} // namespace - -BackendFactory &SndIOBackendFactory::getFactory() -{ - static SndIOBackendFactory factory{}; - return factory; -} - -bool SndIOBackendFactory::init() -{ return true; } - -bool SndIOBackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void SndIOBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(sndio_device, sizeof(sndio_device)); - break; - } -} - -BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new SndioPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new SndioCapture{device}}; - return nullptr; -} diff --git a/Alc/backends/sndio.h b/Alc/backends/sndio.h deleted file mode 100644 index 1ed63d5e..00000000 --- a/Alc/backends/sndio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SNDIO_H -#define BACKENDS_SNDIO_H - -#include "backends/base.h" - -struct SndIOBackendFactory 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_SNDIO_H */ diff --git a/Alc/backends/solaris.cpp b/Alc/backends/solaris.cpp deleted file mode 100644 index 584f6e66..00000000 --- a/Alc/backends/solaris.cpp +++ /dev/null @@ -1,302 +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 "backends/solaris.h" - -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/time.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> -#include <unistd.h> -#include <errno.h> -#include <poll.h> -#include <math.h> - -#include <thread> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "threads.h" -#include "vector.h" -#include "compat.h" - -#include <sys/audioio.h> - - -namespace { - -constexpr ALCchar solaris_device[] = "Solaris Default"; - -std::string solaris_driver{"/dev/audio"}; - - -struct SolarisBackend final : public BackendBase { - SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { } - ~SolarisBackend() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - int mFd{-1}; - - al::vector<ALubyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(SolarisBackend) -}; - -SolarisBackend::~SolarisBackend() -{ - if(mFd != -1) - close(mFd); - mFd = -1; -} - -int SolarisBackend::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const int frame_size{mDevice->frameSizeFromFmt()}; - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = mFd; - pollitem.events = POLLOUT; - - unlock(); - int pret{poll(&pollitem, 1, 1000)}; - lock(); - if(pret < 0) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s", - strerror(errno)); - break; - } - else if(pret == 0) - { - WARN("poll timeout\n"); - continue; - } - - ALubyte *write_ptr{mBuffer.data()}; - size_t to_write{mBuffer.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) - { - ssize_t wrote{write(mFd, write_ptr, to_write)}; - if(wrote < 0) - { - 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)); - break; - } - - to_write -= wrote; - write_ptr += wrote; - } - } - unlock(); - - return 0; -} - - -ALCenum SolarisBackend::open(const ALCchar *name) -{ - if(!name) - name = solaris_device; - else if(strcmp(name, solaris_device) != 0) - return ALC_INVALID_VALUE; - - mFd = ::open(solaris_driver.c_str(), O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", solaris_driver.c_str(), strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - return ALC_NO_ERROR; -} - -ALCboolean SolarisBackend::reset() -{ - audio_info_t info; - AUDIO_INITINFO(&info); - - info.play.sample_rate = mDevice->Frequency; - - if(mDevice->FmtChans != DevFmtMono) - mDevice->FmtChans = DevFmtStereo; - ALsizei numChannels{mDevice->channelsFromFmt()}; - info.play.channels = numChannels; - - 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; - } - - ALsizei frameSize{numChannels * mDevice->bytesFromFmt()}; - info.play.buffer_size = mDevice->BufferSize * frameSize; - - if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) - { - ERR("ioctl failed: %s\n", strerror(errno)); - return ALC_FALSE; - } - - if(mDevice->channelsFromFmt() != (ALsizei)info.play.channels) - { - ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - info.play.channels); - return ALC_FALSE; - } - - if(!((info.play.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))) - { - ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType), - info.play.precision, info.play.encoding); - return ALC_FALSE; - } - - mDevice->Frequency = info.play.sample_rate; - mDevice->BufferSize = info.play.buffer_size / frameSize; - mDevice->UpdateSize = mDevice->BufferSize / 2; - - SetDefaultChannelOrder(mDevice); - - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); - - return ALC_TRUE; -} - -ALCboolean SolarisBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void SolarisBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - if(ioctl(mFd, AUDIO_DRAIN) < 0) - ERR("Error draining device: %s\n", strerror(errno)); -} - -} // namespace - -BackendFactory &SolarisBackendFactory::getFactory() -{ - static SolarisBackendFactory factory{}; - return factory; -} - -bool SolarisBackendFactory::init() -{ - if(auto devopt = ConfigValueStr(nullptr, "solaris", "device")) - solaris_driver = std::move(*devopt); - return true; -} - -bool SolarisBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void SolarisBackendFactory::probe(DevProbe type, std::string *outnames) -{ - 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 DevProbe::Capture: - break; - } -} - -BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new SolarisBackend{device}}; - return nullptr; -} diff --git a/Alc/backends/solaris.h b/Alc/backends/solaris.h deleted file mode 100644 index 98b10593..00000000 --- a/Alc/backends/solaris.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SOLARIS_H -#define BACKENDS_SOLARIS_H - -#include "backends/base.h" - -struct SolarisBackendFactory 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_SOLARIS_H */ diff --git a/Alc/backends/wasapi.cpp b/Alc/backends/wasapi.cpp deleted file mode 100644 index bd009463..00000000 --- a/Alc/backends/wasapi.cpp +++ /dev/null @@ -1,1763 +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 - */ - -#include "config.h" - -#include "backends/wasapi.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <wtypes.h> -#include <mmdeviceapi.h> -#include <audioclient.h> -#include <cguid.h> -#include <devpropdef.h> -#include <mmreg.h> -#include <propsys.h> -#include <propkey.h> -#include <devpkey.h> -#ifndef _WAVEFORMATEXTENSIBLE_ -#include <ks.h> -#include <ksmedia.h> -#endif - -#include <deque> -#include <mutex> -#include <atomic> -#include <thread> -#include <vector> -#include <string> -#include <future> -#include <algorithm> -#include <functional> -#include <condition_variable> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "compat.h" -#include "converter.h" -#include "threads.h" - - -/* Some headers seem to define these as macros for __uuidof, which is annoying - * since some headers don't declare them at all. Hopefully the ifdef is enough - * to tell if they need to be declared. - */ -#ifndef KSDATAFORMAT_SUBTYPE_PCM -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif -#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -#endif - -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 ); - - -namespace { - -#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 X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) - -#define REFTIME_PER_SEC ((REFERENCE_TIME)10000000) - -#define DEVNAME_HEAD "OpenAL Soft on " - - -/* 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) -{ - return (val*new_scale + old_scale-1) / old_scale; -} - - -struct PropVariant { - PROPVARIANT mProp; - -public: - PropVariant() { PropVariantInit(&mProp); } - ~PropVariant() { clear(); } - - void clear() { PropVariantClear(&mProp); } - - PROPVARIANT* get() noexcept { return &mProp; } - - PROPVARIANT& operator*() noexcept { return mProp; } - const PROPVARIANT& operator*() const noexcept { return mProp; } - - PROPVARIANT* operator->() noexcept { return &mProp; } - const PROPVARIANT* operator->() const noexcept { return &mProp; } -}; - -struct DevMap { - std::string name; - std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent. - std::wstring devid; - - template<typename T0, typename T1, typename T2> - DevMap(T0&& name_, T1&& guid_, T2&& devid_) - : name{std::forward<T0>(name_)} - , endpoint_guid{std::forward<T1>(guid_)} - , devid{std::forward<T2>(devid_)} - { } -}; - -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; - - -using NameGUIDPair = std::pair<std::string,std::string>; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) -{ - std::string name{DEVNAME_HEAD}; - std::string guid; - - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); - if(FAILED(hr)) - { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return { name+"Unknown Device Name", "Unknown Device GUID" }; - } - - PropVariant pvprop; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get()); - if(FAILED(hr)) - { - WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += "Unknown Device Name"; - } - 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"; - } - - pvprop.clear(); - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get()); - if(FAILED(hr)) - { - WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = "Unknown Device GUID"; - } - 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"; - } - - ps->Release(); - - return {name, guid}; -} - -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) -{ - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); - if(FAILED(hr)) - { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; - } - - PropVariant pvform; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(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 - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); - - ps->Release(); -} - - -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); - - int count{1}; - std::string newname{basename}; - while(checkName(list, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(guidstr), devid); - const DevMap &newentry = list.back(); - - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); -} - -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; - - HRESULT hr = device->GetId(&devid); - if(FAILED(hr)) - { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; - } - - return devid; -} - -HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list) -{ - IMMDeviceCollection *coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)}; - if(FAILED(hr)) - { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return hr; - } - - 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) - { - defdevid = get_device_id(defdev); - if(defdevid) - add_device(defdev, defdevid, list); - } - - for(UINT i{0};i < count;++i) - { - IMMDevice *device; - hr = coll->Item(i, &device); - if(FAILED(hr)) continue; - - WCHAR *devid{get_device_id(device)}; - if(devid) - { - if(!defdevid || wcscmp(devid, defdevid) != 0) - add_device(device, devid, list); - CoTaskMemFree(devid); - } - device->Release(); - } - - if(defdev) defdev->Release(); - if(defdevid) CoTaskMemFree(defdevid); - coll->Release(); - - return S_OK; -} - - -bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) -{ - *out = WAVEFORMATEXTENSIBLE{}; - if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - { - *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format); - out->Format.cbSize = sizeof(*out) - sizeof(out->Format); - } - else if(in->wFormatTag == WAVE_FORMAT_PCM) - { - out->Format = *in; - out->Format.cbSize = 0; - out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) - { - out->Format = *in; - out->Format.cbSize = 0; - out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - } - else - { - ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); - return false; - } - return true; -} - -void TraceFormat(const char *msg, const WAVEFORMATEX *format) -{ - constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; - if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) - { - 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" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n" - " Samples = %d\n" - " ChannelMask = 0x%lx\n" - " SubFormat = %s\n", - msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec, - fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, - fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, - GuidPrinter{fmtex->SubFormat}.c_str()); - } - else - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n", - msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec, - format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize); -} - - -enum class MsgType : unsigned int { - OpenDevice, - ResetDevice, - StartDevice, - StopDevice, - CloseDevice, - EnumeratePlayback, - EnumerateCapture, - QuitThread, - - Count -}; - -constexpr char MessageStr[static_cast<unsigned int>(MsgType::Count)][20]{ - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Playback", - "Enumerate Capture", - "Quit" -}; - - -/* Proxy interface used by the message handler. */ -struct WasapiProxy { - virtual HRESULT openProxy() = 0; - virtual void closeProxy() = 0; - - virtual HRESULT resetProxy() = 0; - virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; - - struct Msg { - MsgType mType; - WasapiProxy *mProxy; - std::promise<HRESULT> mPromise; - }; - static std::deque<Msg> mMsgQueue; - static std::mutex mMsgQueueLock; - static std::condition_variable mMsgQueueCond; - - std::future<HRESULT> pushMessage(MsgType type) - { - 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)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static std::future<HRESULT> pushMessageStatic(MsgType type) - { - 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)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static bool popMessage(Msg &msg) - { - std::unique_lock<std::mutex> lock{mMsgQueueLock}; - while(mMsgQueue.empty()) - mMsgQueueCond.wait(lock); - msg = std::move(mMsgQueue.front()); - mMsgQueue.pop_front(); - return msg.mType != MsgType::QuitThread; - } - - static int messageHandler(std::promise<HRESULT> *promise); -}; -std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue; -std::mutex WasapiProxy::mMsgQueueLock; -std::condition_variable WasapiProxy::mMsgQueueCond; - -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)}; - if(FAILED(hr)) - { - WARN("Failed to create IMMDeviceEnumerator instance: 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)) - { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", - MessageStr[static_cast<unsigned int>(msg.mType)], static_cast<unsigned int>(msg.mType), - msg.mProxy); - - 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(); - msg.mPromise.set_value(hr); - - if(FAILED(hr)) - { - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - } - continue; - - case MsgType::ResetDevice: - hr = msg.mProxy->resetProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StartDevice: - hr = msg.mProxy->startProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StopDevice: - msg.mProxy->stopProxy(); - msg.mPromise.set_value(S_OK); - continue; - - 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); - - Enumerator->Release(); - Enumerator = nullptr; - } - - 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; - } - } - TRACE("Message loop finished\n"); - - return 0; -} - - -struct WasapiPlayback final : public BackendBase, WasapiProxy { - WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~WasapiPlayback() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; - void closeProxy() override; - - ALCboolean reset() override; - HRESULT resetProxy() override; - ALCboolean 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}; - HANDLE mNotifyEvent{nullptr}; - - std::atomic<UINT32> mPadding{0u}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WasapiPlayback) -}; - -WasapiPlayback::~WasapiPlayback() -{ - pushMessage(MsgType::CloseDevice).wait(); - - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int WasapiPlayback::mixerProc() -{ - 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); - return 1; - } - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - const ALuint update_size{mDevice->UpdateSize}; - const UINT32 buffer_len{mDevice->BufferSize}; - while(!mKillNow.load(std::memory_order_relaxed)) - { - UINT32 written; - hr = mClient->GetCurrentPadding(&written); - if(FAILED(hr)) - { - ERR("Failed to get padding: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr); - break; - } - mPadding.store(written, std::memory_order_relaxed); - - ALuint len{buffer_len - written}; - if(len < update_size) - { - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - continue; - } - - BYTE *buffer; - hr = mRender->GetBuffer(len, &buffer); - if(SUCCEEDED(hr)) - { - lock(); - aluMixData(mDevice, buffer, len); - mPadding.store(written + len, std::memory_order_relaxed); - unlock(); - 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); - break; - } - } - mPadding.store(0u, std::memory_order_release); - - CoUninitialize(); - return 0; -} - - -ALCenum WasapiPlayback::open(const ALCchar *name) -{ - HRESULT hr{S_OK}; - - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) - { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) - { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - - hr = E_FAIL; - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == PlaybackDevices.cend()) - { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == PlaybackDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } - } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - return ALC_NO_ERROR; -} - -HRESULT WasapiPlayback::openProxy() -{ - 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(eRender, eMultimedia, &mMMDev); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); - } - 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; - } - - return hr; -} - -void WasapiPlayback::closeProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; -} - - -ALCboolean WasapiPlayback::reset() -{ - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiPlayback::resetProxy() -{ - if(mClient) - mClient->Release(); - mClient = nullptr; - - void *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); - - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); - if(FAILED(hr)) - { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; - } - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - 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}; - - if(!mDevice->Flags.get<FrequencyRequest>()) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!mDevice->Flags.get<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)) - mDevice->FmtChans = DevFmtX71; - else - ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); - } - - 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; - } - 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; - } - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - - TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); - if(FAILED(hr)) - { - ERR("Failed to check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); - } - if(FAILED(hr)) - { - ERR("Failed to find a supported format: 0x%08lx\n", hr); - return hr; - } - - if(wfx != nullptr) - { - TraceFormat("Got playback format", wfx); - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - 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; - 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)) - { - if(OutputType.Format.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - mDevice->FmtType = DevFmtInt; - else - { - mDevice->FmtType = DevFmtShort; - OutputType.Format.wBitsPerSample = 16; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - mDevice->FmtType = DevFmtFloat; - OutputType.Format.wBitsPerSample = 32; - } - else - { - ERR("Unhandled format sub-type\n"); - mDevice->FmtType = DevFmtShort; - if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - } - - EndpointFormFactor formfactor = UnknownFormFactor; - get_device_formfactor(mMMDev, &formfactor); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - (formfactor == Headphones || formfactor == Headset)); - - SetDefaultWFXChannelOrder(mDevice); - - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 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); - if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); - if(FAILED(hr)) - { - ERR("Failed to get audio buffer info: 0x%08lx\n", hr); - return hr; - } - - /* Find the nearest multiple of the period size to the update size */ - if(min_per < per_time) - min_per *= maxi64((per_time + min_per/2) / min_per, 1); - min_len = (UINT32)ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC); - min_len = minu(min_len, buffer_len/2); - - mDevice->UpdateSize = min_len; - mDevice->BufferSize = buffer_len; - - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - - return hr; -} - - -ALCboolean WasapiPlayback::start() -{ - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiPlayback::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr = mClient->Start(); - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - void *ptr; - hr = mClient->GetService(IID_IAudioRenderClient, &ptr); - if(SUCCEEDED(hr)) - { - mRender = 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; - } - } - - if(FAILED(hr)) - mClient->Stop(); - - return hr; -} - - -void WasapiPlayback::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiPlayback::stopProxy() -{ - if(!mRender || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mRender->Release(); - mRender = nullptr; - mClient->Stop(); -} - - -ClockLatency WasapiPlayback::getClockLatency() -{ - ClockLatency ret; - - lock(); - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; - ret.Latency /= mDevice->Frequency; - unlock(); - - return ret; -} - - -struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~WasapiCapture() override; - - int recordProc(); - - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; - void closeProxy() override; - - HRESULT resetProxy() override; - ALCboolean start() override; - HRESULT startProxy() override; - void stop() override; - void stopProxy() override; - - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::wstring mDevId; - - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioCaptureClient *mCapture{nullptr}; - HANDLE mNotifyEvent{nullptr}; - - ChannelConverterPtr mChannelConv; - SampleConverterPtr mSampleConv; - RingBufferPtr mRing; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WasapiCapture) -}; - -WasapiCapture::~WasapiCapture() -{ - pushMessage(MsgType::CloseDevice).wait(); - - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; -} - - -FORCE_ALIGN int WasapiCapture::recordProc() -{ - 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); - return 1; - } - - althrd_setname(RECORD_THREAD_NAME); - - al::vector<float> samples; - while(!mKillNow.load(std::memory_order_relaxed)) - { - UINT32 avail; - hr = mCapture->GetNextPacketSize(&avail); - if(FAILED(hr)) - ERR("Failed to get next packet size: 0x%08lx\n", hr); - else if(avail > 0) - { - UINT32 numsamples; - DWORD flags; - BYTE *rdata; - - hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); - if(FAILED(hr)) - ERR("Failed to get capture buffer: 0x%08lx\n", hr); - else - { - if(mChannelConv) - { - samples.resize(numsamples*2); - mChannelConv->convert(rdata, samples.data(), numsamples); - rdata = reinterpret_cast<BYTE*>(samples.data()); - } - - auto data = mRing->getWriteVector(); - - size_t dstframes; - if(mSampleConv) - { - const ALvoid *srcdata{rdata}; - auto srcframes = static_cast<ALsizei>(numsamples); - - dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast<ALsizei>(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 - * block was filled, and there's space in the second - * dest block, do another run for the second block. - */ - dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast<ALsizei>(minz(data.second.len, INT_MAX))); - } - } - else - { - const auto framesize = static_cast<ALuint>(mDevice->frameSizeFromFmt()); - size_t len1 = minz(data.first.len, numsamples); - size_t len2 = minz(data.second.len, numsamples-len1); - - memcpy(data.first.buf, rdata, len1*framesize); - if(len2 > 0) - memcpy(data.second.buf, rdata+len1*framesize, len2*framesize); - dstframes = len1 + len2; - } - - mRing->writeAdvance(dstframes); - - hr = mCapture->ReleaseBuffer(numsamples); - if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr); - } - } - - if(FAILED(hr)) - { - aluHandleDisconnect(mDevice, "Failed to capture samples: 0x%08lx", hr); - break; - } - - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - } - - CoUninitialize(); - return 0; -} - - -ALCenum WasapiCapture::open(const ALCchar *name) -{ - HRESULT hr{S_OK}; - - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) - { - ERR("Failed to create notify event: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == CaptureDevices.cend()) - { - 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(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } - - hr = pushMessage(MsgType::ResetDevice).get(); - if(FAILED(hr)) - { - if(hr == E_OUTOFMEMORY) - return ALC_OUT_OF_MEMORY; - return ALC_INVALID_VALUE; - } - - return ALC_NO_ERROR; -} - -HRESULT WasapiCapture::openProxy() -{ - 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); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); - } - 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; - } - - 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; - 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); - - // 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); - - WAVEFORMATEXTENSIBLE OutputType; - OutputType.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 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; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format); - - TraceFormat("Requesting capture format", &OutputType.Format); - WAVEFORMATEX *wfx; - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); - if(FAILED(hr)) - { - ERR("Failed to check format support: 0x%08lx\n", hr); - return hr; - } - - mSampleConv = nullptr; - mChannelConv = nullptr; - - 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))) - { - 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; - } - - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - } - - DevFmtType srcType; - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - if(OutputType.Format.wBitsPerSample == 8) - srcType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - srcType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - srcType = DevFmtInt; - else - { - ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample); - return E_FAIL; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - if(OutputType.Format.wBitsPerSample == 32) - srcType = DevFmtFloat; - else - { - ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample); - return E_FAIL; - } - } - else - { - ERR("Unhandled format sub-type\n"); - return E_FAIL; - } - - if(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2) - { - mChannelConv = CreateChannelConverter(srcType, DevFmtStereo, mDevice->FmtChans); - if(!mChannelConv) - { - ERR("Failed to create %s stereo-to-mono converter\n", DevFmtTypeString(srcType)); - return E_FAIL; - } - TRACE("Created %s stereo-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) - { - mChannelConv = CreateChannelConverter(srcType, DevFmtMono, mDevice->FmtChans); - if(!mChannelConv) - { - ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); - return E_FAIL; - } - TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); - srcType = DevFmtFloat; - } - - if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) - { - mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), - OutputType.Format.nSamplesPerSec, mDevice->Frequency, BSinc24Resampler); - 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); - 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); - } - - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); - if(FAILED(hr)) - { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); - return hr; - } - - UINT32 buffer_len; - REFERENCE_TIME min_per; - hr = mClient->GetDevicePeriod(&min_per, nullptr); - if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); - if(FAILED(hr)) - { - 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->BufferSize = buffer_len; - - buffer_len = maxu(mDevice->BufferSize, buffer_len); - mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("Failed to allocate capture ring buffer\n"); - return E_OUTOFMEMORY; - } - - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - - return hr; -} - - -ALCboolean WasapiCapture::start() -{ - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; -} - -HRESULT WasapiCapture::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - void *ptr; - hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); - if(SUCCEEDED(hr)) - { - mCapture = 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; - } - } - - if(FAILED(hr)) - { - mClient->Stop(); - mClient->Reset(); - } - - return hr; -} - - -void WasapiCapture::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiCapture::stopProxy() -{ - if(!mCapture || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mCapture->Release(); - mCapture = nullptr; - mClient->Stop(); - mClient->Reset(); -} - - -ALCuint WasapiCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } - -ALCenum WasapiCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -} // namespace - - -bool WasapiBackendFactory::init() -{ - static HRESULT InitResult{E_FAIL}; - - if(FAILED(InitResult)) try - { - std::promise<HRESULT> promise; - auto future = promise.get_future(); - - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); - InitResult = future.get(); - } - catch(...) { - } - - return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; -} - -bool WasapiBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void WasapiBackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - HRESULT hr{}; - switch(type) - { - case DevProbe::Playback: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get(); - if(SUCCEEDED(hr)) - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get(); - if(SUCCEEDED(hr)) - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } -} - -BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WasapiPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new WasapiCapture{device}}; - return nullptr; -} - -BackendFactory &WasapiBackendFactory::getFactory() -{ - static WasapiBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/wasapi.h b/Alc/backends/wasapi.h deleted file mode 100644 index 067dd259..00000000 --- a/Alc/backends/wasapi.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WASAPI_H -#define BACKENDS_WASAPI_H - -#include "backends/base.h" - -struct WasapiBackendFactory 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_WASAPI_H */ diff --git a/Alc/backends/wave.cpp b/Alc/backends/wave.cpp deleted file mode 100644 index 67ed7e79..00000000 --- a/Alc/backends/wave.cpp +++ /dev/null @@ -1,402 +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 "backends/wave.h" - -#include <algorithm> -#include <atomic> -#include <cerrno> -#include <chrono> -#include <cstdint> -#include <cstdio> -#include <cstring> -#include <exception> -#include <functional> -#include <thread> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alu.h" -#include "compat.h" -#include "logging.h" -#include "threads.h" -#include "vector.h" - - -namespace { - -using std::chrono::seconds; -using std::chrono::milliseconds; -using std::chrono::nanoseconds; - -constexpr ALCchar waveDevice[] = "Wave File Writer"; - -constexpr ALubyte SUBTYPE_PCM[]{ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71 -}; -constexpr ALubyte SUBTYPE_FLOAT[]{ - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, - 0x00, 0x38, 0x9b, 0x71 -}; - -constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{ - 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, - 0xca, 0x00, 0x00, 0x00 -}; - -constexpr ALubyte 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) -{ - ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) }; - fwrite(data, 1, 2, f); -} - -void fwrite32le(ALuint 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) }; - fwrite(data, 1, 4, f); -} - - -struct WaveBackend final : public BackendBase { - WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { } - ~WaveBackend() override; - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - FILE *mFile{nullptr}; - long mDataStart{-1}; - - al::vector<ALbyte> mBuffer; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WaveBackend) -}; - -WaveBackend::~WaveBackend() -{ - if(mFile) - fclose(mFile); - mFile = nullptr; -} - -int WaveBackend::mixerProc() -{ - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - - althrd_setname(MIXER_THREAD_NAME); - - const ALsizei 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)) - { - auto now = std::chrono::steady_clock::now(); - - /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast<seconds>((now-start) * - mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) - { - std::this_thread::sleep_for(restTime); - continue; - } - while(avail-done >= mDevice->UpdateSize) - { - lock(); - aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize); - unlock(); - done += mDevice->UpdateSize; - - if(!IS_LITTLE_ENDIAN) - { - const ALsizei bytesize{mDevice->bytesFromFmt()}; - ALsizei i; - - if(bytesize == 2) - { - ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data()); - const auto len = static_cast<ALsizei>(mBuffer.size() / 2); - for(i = 0;i < len;i++) - { - ALushort samp = samples[i]; - samples[i] = (samp>>8) | (samp<<8); - } - } - else if(bytesize == 4) - { - ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data()); - const auto len = static_cast<ALsizei>(mBuffer.size() / 4); - for(i = 0;i < len;i++) - { - ALuint samp = samples[i]; - samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) | - ((samp<<8)&0x00ff0000) | (samp<<24); - } - } - } - - size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - (void)fs; - if(ferror(mFile)) - { - ERR("Error writing to file\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); - break; - } - } - - /* For every completed second, increment the start time and reduce the - * samples done. This prevents the difference between the start time - * and current time from growing too large, while maintaining the - * correct number of samples to render. - */ - if(done >= mDevice->Frequency) - { - seconds s{done/mDevice->Frequency}; - start += s; - done -= mDevice->Frequency*s.count(); - } - } - - return 0; -} - -ALCenum WaveBackend::open(const ALCchar *name) -{ - const char *fname{GetConfigValue(nullptr, "wave", "file", "")}; - if(!fname[0]) return ALC_INVALID_VALUE; - - if(!name) - name = waveDevice; - else if(strcmp(name, waveDevice) != 0) - return ALC_INVALID_VALUE; - -#ifdef _WIN32 - { - std::wstring wname = utf8_to_wstr(fname); - mFile = _wfopen(wname.c_str(), L"wb"); - } -#else - mFile = fopen(fname, "wb"); -#endif - if(!mFile) - { - ERR("Could not open file '%s': %s\n", fname, strerror(errno)); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = name; - - return ALC_NO_ERROR; -} - -ALCboolean WaveBackend::reset() -{ - ALuint channels=0, bytes=0, chanmask=0; - int isbformat = 0; - size_t val; - - fseek(mFile, 0, SEEK_SET); - clearerr(mFile); - - if(GetConfigValueBool(nullptr, "wave", "bformat", 0)) - { - mDevice->FmtChans = DevFmtAmbi3D; - mDevice->mAmbiOrder = 1; - } - - 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; - } - switch(mDevice->FmtChans) - { - case DevFmtMono: chanmask = 0x04; break; - case DevFmtStereo: chanmask = 0x01 | 0x02; break; - case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; - case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; - case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break; - case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; - case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; - case DevFmtAmbi3D: - /* .amb output requires FuMa */ - mDevice->mAmbiOrder = mini(mDevice->mAmbiOrder, 3); - mDevice->mAmbiLayout = AmbiLayout::FuMa; - mDevice->mAmbiScale = AmbiNorm::FuMa; - isbformat = 1; - chanmask = 0; - break; - } - bytes = mDevice->bytesFromFmt(); - channels = mDevice->channelsFromFmt(); - - rewind(mFile); - - fputs("RIFF", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close - - fputs("WAVE", mFile); - - fputs("fmt ", mFile); - fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE - - // 16-bit val, format type id (extensible: 0xFFFE) - fwrite16le(0xFFFE, mFile); - // 16-bit val, channel count - fwrite16le(channels, mFile); - // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile); - // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile); - // 16-bit val, frame size - fwrite16le(channels * bytes, mFile); - // 16-bit val, bits per sample - fwrite16le(bytes * 8, mFile); - // 16-bit val, extra byte count - fwrite16le(22, mFile); - // 16-bit val, valid bits per sample - fwrite16le(bytes * 8, mFile); - // 32-bit val, channel mask - fwrite32le(chanmask, mFile); - // 16 byte GUID, sub-type format - val = fwrite((mDevice->FmtType == DevFmtFloat) ? - (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : - (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); - (void)val; - - fputs("data", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close - - if(ferror(mFile)) - { - ERR("Error writing header: %s\n", strerror(errno)); - return ALC_FALSE; - } - mDataStart = ftell(mFile); - - SetDefaultWFXChannelOrder(mDevice); - - const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; - mBuffer.resize(bufsize); - - return ALC_TRUE; -} - -ALCboolean WaveBackend::start() -{ - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WaveBackend::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - long size{ftell(mFile)}; - if(size > 0) - { - long dataLen{size - mDataStart}; - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(dataLen, mFile); // 'data' header len - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(size-8, mFile); // 'WAVE' header len - } -} - -} // namespace - - -bool WaveBackendFactory::init() -{ return true; } - -bool WaveBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -void WaveBackendFactory::probe(DevProbe type, std::string *outnames) -{ - switch(type) - { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(waveDevice, sizeof(waveDevice)); - break; - case DevProbe::Capture: - break; - } -} - -BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WaveBackend{device}}; - return nullptr; -} - -BackendFactory &WaveBackendFactory::getFactory() -{ - static WaveBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/wave.h b/Alc/backends/wave.h deleted file mode 100644 index b9b62d7f..00000000 --- a/Alc/backends/wave.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WAVE_H -#define BACKENDS_WAVE_H - -#include "backends/base.h" - -struct WaveBackendFactory 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_WAVE_H */ diff --git a/Alc/backends/winmm.cpp b/Alc/backends/winmm.cpp deleted file mode 100644 index cd32e95b..00000000 --- a/Alc/backends/winmm.cpp +++ /dev/null @@ -1,640 +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 "backends/winmm.h" - -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - -#include <windows.h> -#include <mmsystem.h> - -#include <array> -#include <atomic> -#include <thread> -#include <vector> -#include <string> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "threads.h" -#include "compat.h" - -#ifndef WAVE_FORMAT_IEEE_FLOAT -#define WAVE_FORMAT_IEEE_FLOAT 0x0003 -#endif - -namespace { - -#define DEVNAME_HEAD "OpenAL Soft on " - - -al::vector<std::string> PlaybackDevices; -al::vector<std::string> CaptureDevices; - -bool checkName(const al::vector<std::string> &list, const std::string &name) -{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); } - -void ProbePlaybackDevices(void) -{ - PlaybackDevices.clear(); - - ALuint numdevs{waveOutGetNumDevs()}; - PlaybackDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) - { - std::string dname; - - WAVEOUTCAPSW WaveCaps{}; - if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) - { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; - - int count{1}; - std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - dname = std::move(newname); - - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); - } - PlaybackDevices.emplace_back(std::move(dname)); - } -} - -void ProbeCaptureDevices(void) -{ - CaptureDevices.clear(); - - ALuint numdevs{waveInGetNumDevs()}; - CaptureDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) - { - std::string dname; - - WAVEINCAPSW WaveCaps{}; - if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) - { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; - - int count{1}; - std::string newname{basename}; - while(checkName(CaptureDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - dname = std::move(newname); - - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); - } - CaptureDevices.emplace_back(std::move(dname)); - } -} - - -struct WinMMPlayback final : public BackendBase { - WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { } - ~WinMMPlayback() override; - - static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); - - int mixerProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::atomic<ALuint> mWritable{0u}; - al::semaphore mSem; - int mIdx{0}; - std::array<WAVEHDR,4> mWaveBuffer{}; - - HWAVEOUT mOutHdl{nullptr}; - - WAVEFORMATEX mFormat{}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WinMMPlayback) -}; - -WinMMPlayback::~WinMMPlayback() -{ - if(mOutHdl) - waveOutClose(mOutHdl); - mOutHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); -} - - -void CALLBACK WinMMPlayback::waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); } - -/* WinMMPlayback::waveOutProc - * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is - * completed and returns to the application (for more data) - */ -void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) -{ - if(msg != WOM_DONE) return; - mWritable.fetch_add(1, std::memory_order_acq_rel); - mSem.post(); -} - -FORCE_ALIGN int WinMMPlayback::mixerProc() -{ - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - ALsizei todo = mWritable.load(std::memory_order_acquire); - if(todo < 1) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - int widx{mIdx}; - do { - WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); - - aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize); - mWritable.fetch_sub(1, std::memory_order_acq_rel); - waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); - } while(--todo); - mIdx = widx; - } - unlock(); - - return 0; -} - - -ALCenum WinMMPlayback::open(const ALCchar *name) -{ - if(PlaybackDevices.empty()) - ProbePlaybackDevices(); - - // Find the Device ID matching the deviceName if valid - auto iter = name ? - std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : - PlaybackDevices.cbegin(); - if(iter == PlaybackDevices.cend()) return ALC_INVALID_VALUE; - auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter)); - -retry_open: - mFormat = WAVEFORMATEX{}; - if(mDevice->FmtType == DevFmtFloat) - { - mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - mFormat.wBitsPerSample = 32; - } - else - { - mFormat.wFormatTag = WAVE_FORMAT_PCM; - if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; - else - mFormat.wBitsPerSample = 16; - } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMPlayback::waveOutProcC, - reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { - if(mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - ERR("waveOutOpen failed: %u\n", res); - return ALC_INVALID_VALUE; - } - - mDevice->DeviceName = PlaybackDevices[DeviceID]; - return ALC_NO_ERROR; -} - -ALCboolean WinMMPlayback::reset() -{ - mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} * - mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3; - mDevice->UpdateSize = mDevice->BufferSize / 4; - mDevice->Frequency = mFormat.nSamplesPerSec; - - if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) - { - if(mFormat.wBitsPerSample == 32) - mDevice->FmtType = DevFmtFloat; - else - { - ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; - } - } - else if(mFormat.wFormatTag == WAVE_FORMAT_PCM) - { - if(mFormat.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(mFormat.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else - { - ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; - } - } - else - { - ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); - return ALC_FALSE; - } - - if(mFormat.nChannels == 2) - mDevice->FmtChans = DevFmtStereo; - else if(mFormat.nChannels == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unhandled channel count: %d\n", mFormat.nChannels); - return ALC_FALSE; - } - SetDefaultWFXChannelOrder(mDevice); - - ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()}; - - al_free(mWaveBuffer[0].lpData); - mWaveBuffer[0] = WAVEHDR{}; - 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++) - { - mWaveBuffer[i] = WAVEHDR{}; - mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; - mWaveBuffer[i].dwBufferLength = BufferSize; - } - mIdx = 0; - - return ALC_TRUE; -} - -ALCboolean 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); - - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WinMMPlayback::stop() -{ - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - mThread.join(); - - 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)); } - ); - mWritable.store(0, std::memory_order_release); -} - - -struct WinMMCapture final : public BackendBase { - WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { } - ~WinMMCapture() override; - - static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); - - int captureProc(); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::atomic<ALuint> mReadable{0u}; - al::semaphore mSem; - int mIdx{0}; - std::array<WAVEHDR,4> mWaveBuffer{}; - - HWAVEIN mInHdl{nullptr}; - - RingBufferPtr mRing{nullptr}; - - WAVEFORMATEX mFormat{}; - - std::atomic<bool> mKillNow{true}; - std::thread mThread; - - DEF_NEWDEL(WinMMCapture) -}; - -WinMMCapture::~WinMMCapture() -{ - // Close the Wave device - if(mInHdl) - waveInClose(mInHdl); - mInHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); -} - -void CALLBACK WinMMCapture::waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); } - -/* WinMMCapture::waveInProc - * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is - * completed and returns to the application (with more data). - */ -void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) -{ - if(msg != WIM_DATA) return; - mReadable.fetch_add(1, std::memory_order_acq_rel); - mSem.post(); -} - -int WinMMCapture::captureProc() -{ - althrd_setname(RECORD_THREAD_NAME); - - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) - { - ALuint todo{mReadable.load(std::memory_order_acquire)}; - if(todo < 1) - { - unlock(); - mSem.wait(); - lock(); - continue; - } - - int widx{mIdx}; - do { - WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); - - mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); - mReadable.fetch_sub(1, std::memory_order_acq_rel); - waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); - } while(--todo); - mIdx = widx; - } - unlock(); - - return 0; -} - - -ALCenum WinMMCapture::open(const ALCchar *name) -{ - if(CaptureDevices.empty()) - ProbeCaptureDevices(); - - // Find the Device ID matching the deviceName if valid - auto iter = name ? - std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : - CaptureDevices.cbegin(); - if(iter == CaptureDevices.cend()) return ALC_INVALID_VALUE; - auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter)); - - switch(mDevice->FmtChans) - { - case DevFmtMono: - case DevFmtStereo: - break; - - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - return ALC_INVALID_ENUM; - } - - switch(mDevice->FmtType) - { - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - return ALC_INVALID_ENUM; - } - - mFormat = WAVEFORMATEX{}; - mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ? - WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; - mFormat.nChannels = mDevice->channelsFromFmt(); - mFormat.wBitsPerSample = mDevice->bytesFromFmt() * 8; - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMCapture::waveInProcC, - reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { - ERR("waveInOpen failed: %u\n", res); - return ALC_INVALID_VALUE; - } - - // Ensure each buffer is 50ms each - DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; - BufferSize -= (BufferSize % mFormat.nBlockAlign); - - // 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())); - - mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false); - if(!mRing) return ALC_INVALID_VALUE; - - al_free(mWaveBuffer[0].lpData); - mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4)); - mWaveBuffer[0].dwBufferLength = BufferSize; - for(size_t i{1};i < mWaveBuffer.size();++i) - { - mWaveBuffer[i] = WAVEHDR{}; - mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; - mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; - } - - mDevice->DeviceName = CaptureDevices[DeviceID]; - return ALC_NO_ERROR; -} - -ALCboolean WinMMCapture::start() -{ - try { - for(size_t i{0};i < mWaveBuffer.size();++i) - { - waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - } - - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; - - waveInStart(mInHdl); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -void WinMMCapture::stop() -{ - waveInStop(mInHdl); - - mKillNow.store(true, std::memory_order_release); - if(mThread.joinable()) - { - mSem.post(); - mThread.join(); - } - - waveInReset(mInHdl); - for(size_t i{0};i < mWaveBuffer.size();++i) - waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); - - mReadable.store(0, std::memory_order_release); - mIdx = 0; -} - -ALCenum WinMMCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} - -ALCuint WinMMCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } - -} // namespace - - -bool WinMMBackendFactory::init() -{ return true; } - -bool WinMMBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback || type == BackendType::Capture; } - -void WinMMBackendFactory::probe(DevProbe type, 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); - }; - 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; - } -} - -BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new WinMMPlayback{device}}; - if(type == BackendType::Capture) - return BackendPtr{new WinMMCapture{device}}; - return nullptr; -} - -BackendFactory &WinMMBackendFactory::getFactory() -{ - static WinMMBackendFactory factory{}; - return factory; -} diff --git a/Alc/backends/winmm.h b/Alc/backends/winmm.h deleted file mode 100644 index e357ec19..00000000 --- a/Alc/backends/winmm.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_WINMM_H -#define BACKENDS_WINMM_H - -#include "backends/base.h" - -struct WinMMBackendFactory 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_WINMM_H */ diff --git a/Alc/bformatdec.cpp b/Alc/bformatdec.cpp deleted file mode 100644 index 889bbf3a..00000000 --- a/Alc/bformatdec.cpp +++ /dev/null @@ -1,200 +0,0 @@ - -#include "config.h" - -#include "bformatdec.h" - -#include <algorithm> -#include <array> -#include <cassert> -#include <cmath> -#include <iterator> -#include <numeric> - -#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(ALsizei order) noexcept -> const ALfloat(&)[MAX_AMBI_ORDER+1] -{ - if(order >= 3) return Ambi3DDecoderHFScale3O; - if(order == 2) return Ambi3DDecoderHFScale2O; - return Ambi3DDecoderHFScale; -} - -inline auto GetAmbiScales(AmbDecScale scaletype) noexcept -> const std::array<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 ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mDualBand = allow_2band && (conf->FreqBands == 2); - if(!mDualBand) - mSamples.resize(2); - else - { - ASSUME(inchans > 0); - mSamples.resize(inchans * 2); - mSamplesHF = mSamples.data(); - mSamplesLF = mSamplesHF + inchans; - } - mNumChannels = inchans; - - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+conf->Speakers.size(), 0u, - [](ALuint mask, const ALsizei &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ALfloat xover_norm{conf->XOverFreq / static_cast<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 ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mSamples.resize(2); - mNumChannels = inchans; - - ASSUME(chancount > 0); - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+chancount, 0u, - [](ALuint mask, const ALsizei &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ChannelDec *incoeffs{chancoeffs}; - auto set_coeffs = [this,inchans,&incoeffs](const ALsizei chanidx) noexcept -> void - { - ASSUME(chanidx >= 0); - ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanidx]; - const ALfloat (&coeffs)[MAX_AMBI_CHANNELS] = *(incoeffs++); - - ASSUME(inchans > 0); - std::copy_n(std::begin(coeffs), inchans, std::begin(mtx)); - }; - std::for_each(chanmap, chanmap+chancount, set_coeffs); -} - - -void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer, - const FloatBufferLine *InSamples, const ALsizei SamplesToDo) -{ - if(mDualBand) - { - for(ALuint i{0};i < mNumChannels;i++) - mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i].data(), - SamplesToDo); - - const al::span<const FloatBufferLine> hfsamples{mSamplesHF, mNumChannels}; - const al::span<const FloatBufferLine> lfsamples{mSamplesLF, mNumChannels}; - ALfloat (*mixmtx)[sNumBands][MAX_AMBI_CHANNELS]{mMatrix.Dual}; - ALuint enabled{mEnabled}; - for(FloatBufferLine &outbuf : OutBuffer) - { - if(LIKELY(enabled&1)) - { - MixRowSamples(outbuf, (*mixmtx)[sHFBand], hfsamples, 0, SamplesToDo); - MixRowSamples(outbuf, (*mixmtx)[sLFBand], lfsamples, 0, SamplesToDo); - } - ++mixmtx; - enabled >>= 1; - } - } - else - { - const al::span<const FloatBufferLine> insamples{InSamples, mNumChannels}; - ALfloat (*mixmtx)[MAX_AMBI_CHANNELS]{mMatrix.Single}; - ALuint enabled{mEnabled}; - for(FloatBufferLine &outbuf : OutBuffer) - { - if(LIKELY(enabled&1)) - MixRowSamples(outbuf, *mixmtx, insamples, 0, SamplesToDo); - ++mixmtx; - enabled >>= 1; - } - } -} - - -std::array<ALfloat,MAX_AMBI_ORDER+1> BFormatDec::GetHFOrderScales(const ALsizei in_order, const ALsizei out_order) noexcept -{ - std::array<ALfloat,MAX_AMBI_ORDER+1> ret{}; - - assert(out_order >= in_order); - ASSUME(out_order >= in_order); - - const ALfloat (&target)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(out_order); - const ALfloat (&input)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(in_order); - - for(ALsizei i{0};i < in_order+1;++i) - ret[i] = input[i] / target[i]; - - return ret; -} diff --git a/Alc/bformatdec.h b/Alc/bformatdec.h deleted file mode 100644 index 06974651..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 "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}; - - ALuint mEnabled{0u}; /* Bitfield of enabled channels. */ - - union MatrixU { - ALfloat Dual[MAX_OUTPUT_CHANNELS][sNumBands][MAX_AMBI_CHANNELS]; - ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_CHANNELS]; - } mMatrix{}; - - /* NOTE: BandSplitter filters are unused with single-band decoding */ - BandSplitter mXOver[MAX_AMBI_CHANNELS]; - - al::vector<FloatBufferLine, 16> mSamples; - /* These two alias into Samples */ - FloatBufferLine *mSamplesHF{nullptr}; - FloatBufferLine *mSamplesLF{nullptr}; - - ALuint mNumChannels{0u}; - bool mDualBand{false}; - -public: - BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans, - const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]); - BFormatDec(const ALuint inchans, const ALsizei chancount, - const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS], - const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]); - - /* Decodes the ambisonic input to the given output channels. */ - void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples, - const ALsizei SamplesToDo); - - /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ - static std::array<ALfloat,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALsizei in_order, - const ALsizei out_order) noexcept; - - DEF_NEWDEL(BFormatDec) -}; - -#endif /* BFORMATDEC_H */ diff --git a/Alc/bs2b.cpp b/Alc/bs2b.cpp deleted file mode 100644 index 2d1b96aa..00000000 --- a/Alc/bs2b.cpp +++ /dev/null @@ -1,188 +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 <cmath> -#include <cstring> -#include <algorithm> - -#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 / bs2b->srate); - bs2b->b1_lo = x; - bs2b->a0_lo = G_lo * (1.0f - x) * g; - - x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / 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->last_sample), std::end(bs2b->last_sample), bs2b::t_last_sample{}); -} /* bs2b_clear */ - -void bs2b_cross_feed(struct bs2b *bs2b, float *RESTRICT Left, float *RESTRICT Right, int SamplesToDo) -{ - float lsamples[128][2]; - float rsamples[128][2]; - int base; - - for(base = 0;base < SamplesToDo;) - { - int todo = std::min(128, SamplesToDo-base); - int i; - - /* Process left input */ - lsamples[0][0] = bs2b->a0_lo*Left[0] + - bs2b->b1_lo*bs2b->last_sample[0].lo; - lsamples[0][1] = bs2b->a0_hi*Left[0] + - bs2b->a1_hi*bs2b->last_sample[0].asis + - bs2b->b1_hi*bs2b->last_sample[0].hi; - for(i = 1;i < todo;i++) - { - lsamples[i][0] = bs2b->a0_lo*Left[i] + - bs2b->b1_lo*lsamples[i-1][0]; - lsamples[i][1] = bs2b->a0_hi*Left[i] + - bs2b->a1_hi*Left[i-1] + - bs2b->b1_hi*lsamples[i-1][1]; - } - bs2b->last_sample[0].asis = Left[i-1]; - bs2b->last_sample[0].lo = lsamples[i-1][0]; - bs2b->last_sample[0].hi = lsamples[i-1][1]; - - /* Process right input */ - rsamples[0][0] = bs2b->a0_lo*Right[0] + - bs2b->b1_lo*bs2b->last_sample[1].lo; - rsamples[0][1] = bs2b->a0_hi*Right[0] + - bs2b->a1_hi*bs2b->last_sample[1].asis + - bs2b->b1_hi*bs2b->last_sample[1].hi; - for(i = 1;i < todo;i++) - { - rsamples[i][0] = bs2b->a0_lo*Right[i] + - bs2b->b1_lo*rsamples[i-1][0]; - rsamples[i][1] = bs2b->a0_hi*Right[i] + - bs2b->a1_hi*Right[i-1] + - bs2b->b1_hi*rsamples[i-1][1]; - } - bs2b->last_sample[1].asis = Right[i-1]; - bs2b->last_sample[1].lo = rsamples[i-1][0]; - bs2b->last_sample[1].hi = rsamples[i-1][1]; - - /* Crossfeed */ - for(i = 0;i < todo;i++) - *(Left++) = lsamples[i][1] + rsamples[i][0]; - for(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 e235e765..00000000 --- a/Alc/bs2b.h +++ /dev/null @@ -1,90 +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 last filtered sample. - * [0] - first channel, [1] - second channel - */ - struct t_last_sample { - float asis; - float lo; - float hi; - } last_sample[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 *RESTRICT Left, float *RESTRICT Right, int SamplesToDo); - -#endif /* BS2B_H */ diff --git a/Alc/compat.h b/Alc/compat.h deleted file mode 100644 index 4ffc40bf..00000000 --- a/Alc/compat.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef AL_COMPAT_H -#define AL_COMPAT_H - -#ifdef __cplusplus - -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <array> -#include <string> -#include <fstream> - -inline std::string wstr_to_utf8(const WCHAR *wstr) -{ - std::string ret; - - int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); - if(len > 0) - { - ret.resize(len); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr); - ret.pop_back(); - } - - return ret; -} - -inline std::wstring utf8_to_wstr(const char *str) -{ - std::wstring ret; - - int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - if(len > 0) - { - ret.resize(len); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len); - ret.pop_back(); - } - - return ret; -} - - -namespace al { - -// Windows' std::ifstream fails with non-ANSI paths since the standard only -// specifies names using const char* (or std::string). MSVC has a non-standard -// extension using const wchar_t* (or std::wstring?) to handle Unicode paths, -// but not all Windows compilers support it. So we have to make our own istream -// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions. -class filebuf final : public std::streambuf { - std::array<char_type,4096> mBuffer; - HANDLE mFile{INVALID_HANDLE_VALUE}; - - int_type underflow() override; - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override; - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override; - -public: - filebuf() = default; - ~filebuf() override; - - bool open(const wchar_t *filename, std::ios_base::openmode mode); - bool open(const char *filename, std::ios_base::openmode mode); - - bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } -}; - -// Inherit from std::istream to use our custom streambuf -class ifstream final : public std::istream { - filebuf mStreamBuf; - -public: - ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in); - ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in); - ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ~ifstream() override; - - bool is_open() const noexcept { return mStreamBuf.is_open(); } -}; - -} // namespace al - -#define HAVE_DYNLOAD 1 - -#else /* _WIN32 */ - -#include <fstream> - -namespace al { - -using filebuf = std::filebuf; -using ifstream = std::ifstream; - -} // namespace al - -#if defined(HAVE_DLFCN_H) -#define HAVE_DYNLOAD 1 -#endif - -#endif /* _WIN32 */ - -#include <string> - -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); - -#ifdef HAVE_DYNLOAD -void *LoadLib(const char *name); -void CloseLib(void *handle); -void *GetSymbol(void *handle, const char *name); -#endif - -#endif /* __cplusplus */ - -#endif /* AL_COMPAT_H */ diff --git a/Alc/converter.cpp b/Alc/converter.cpp deleted file mode 100644 index 0f8e8941..00000000 --- a/Alc/converter.cpp +++ /dev/null @@ -1,367 +0,0 @@ - -#include "config.h" - -#include "converter.h" - -#include <algorithm> - -#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 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>(val - 128); } -template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) noexcept -{ return LoadSample<DevFmtShort>(val - 32768); } -template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) noexcept -{ return LoadSample<DevFmtInt>(val - 2147483648u); } - - -template<DevFmtType T> -inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, const size_t srcstep, - const ALsizei samples) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(ALsizei i{0};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 ALsizei 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 fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); } -template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) noexcept -{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); } - -/* Define unsigned output variations. */ -template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) noexcept -{ return StoreSample<DevFmtInt>(val) + 2147483648u; } -template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) noexcept -{ return StoreSample<DevFmtShort>(val) + 32768; } -template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) noexcept -{ return StoreSample<DevFmtByte>(val) + 128; } - -template<DevFmtType T> -inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, const size_t dststep, - const ALsizei samples) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - SampleType *sdst = static_cast<SampleType*>(dst); - for(ALsizei i{0};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 ALsizei 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 ALsizei frames) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(ALsizei i{0};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 ALsizei frames) noexcept -{ - using SampleType = typename DevFmtTypeTraits<T>::Type; - - const SampleType *ssrc = static_cast<const SampleType*>(src); - for(ALsizei i{0};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, ALsizei numchans, - ALsizei srcRate, ALsizei dstRate, Resampler resampler) -{ - if(numchans <= 0 || srcRate <= 0 || dstRate <= 0) - return nullptr; - - void *ptr{al_calloc(16, SampleConverter::Sizeof(numchans))}; - SampleConverterPtr converter{new (ptr) SampleConverter{static_cast<size_t>(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<ALsizei>( - mind(static_cast<ALdouble>(srcRate)/dstRate*FRACTIONONE + 0.5, MAX_PITCH*FRACTIONONE)); - converter->mIncrement = maxi(step, 1); - if(converter->mIncrement == FRACTIONONE) - converter->mResample = Resample_<CopyTag,CTag>; - else - { - if(resampler == BSinc24Resampler) - BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc24); - else if(resampler == BSinc12Resampler) - BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc12); - converter->mResample = SelectResampler(resampler); - } - - return converter; -} - -ALsizei SampleConverter::availableOut(ALsizei srcframes) const -{ - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(-prepcount >= srcframes) - return 0; - srcframes += prepcount; - prepcount = 0; - } - - if(srcframes < 1) - { - /* No output samples if there's no input samples. */ - return 0; - } - - if(prepcount < MAX_RESAMPLE_PADDING*2 && - MAX_RESAMPLE_PADDING*2 - prepcount >= srcframes) - { - /* Not enough input samples to generate an output sample. */ - return 0; - } - - auto DataSize64 = static_cast<uint64_t>(prepcount); - DataSize64 += srcframes; - DataSize64 -= MAX_RESAMPLE_PADDING*2; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= mFracOffset; - - /* If we have a full prep, we can generate at least one sample. */ - return static_cast<ALsizei>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE)); -} - -ALsizei SampleConverter::convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes) -{ - const ALsizei SrcFrameSize{static_cast<ALsizei>(mChan.size()) * mSrcTypeSize}; - const ALsizei DstFrameSize{static_cast<ALsizei>(mChan.size()) * mDstTypeSize}; - const ALsizei increment{mIncrement}; - auto SamplesIn = static_cast<const al::byte*>(*src); - ALsizei NumSrcSamples{*srcframes}; - - FPUCtl mixer_mode{}; - ALsizei pos{0}; - while(pos < dstframes && NumSrcSamples > 0) - { - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(-prepcount >= NumSrcSamples) - { - mSrcPrepCount = prepcount + NumSrcSamples; - NumSrcSamples = 0; - break; - } - SamplesIn += SrcFrameSize*-prepcount; - NumSrcSamples += prepcount; - mSrcPrepCount = 0; - continue; - } - ALint toread{mini(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLE_PADDING*2)}; - - if(prepcount < MAX_RESAMPLE_PADDING*2 && - MAX_RESAMPLE_PADDING*2 - prepcount >= toread) - { - /* Not enough input samples to generate an output sample. Store - * what we're given for later. - */ - for(size_t chan{0u};chan < mChan.size();chan++) - LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, - mChan.size(), mSrcType, toread); - - mSrcPrepCount = prepcount + toread; - NumSrcSamples = 0; - break; - } - - ALfloat *RESTRICT SrcData{mSrcSamples}; - ALfloat *RESTRICT DstData{mDstSamples}; - ALsizei DataPosFrac{mFracOffset}; - auto DataSize64 = static_cast<uint64_t>(prepcount); - DataSize64 += toread; - DataSize64 -= MAX_RESAMPLE_PADDING*2; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= DataPosFrac; - - /* If we have a full prep, we can generate at least one sample. */ - auto DstSize = static_cast<ALsizei>( - clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE)); - DstSize = mini(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. - */ - ALsizei SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS}; - if(SrcDataEnd >= prepcount+toread) - std::fill(std::begin(mChan[chan].PrevSamples), - std::end(mChan[chan].PrevSamples), 0.0f); - else - { - size_t len = mini(MAX_RESAMPLE_PADDING*2, prepcount+toread-SrcDataEnd); - std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+len, - std::end(mChan[chan].PrevSamples), 0.0f); - } - - /* Now resample, and store the result in the output buffer. */ - const ALfloat *ResampledData{mResample(&mState, SrcData+MAX_RESAMPLE_PADDING, - DataPosFrac, increment, DstData, DstSize)}; - - StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize); - } - - /* Update the number of prep samples still available, as well as the - * fractional offset. - */ - DataPosFrac += increment*DstSize; - mSrcPrepCount = mini(prepcount + toread - (DataPosFrac>>FRACTIONBITS), - MAX_RESAMPLE_PADDING*2); - mFracOffset = DataPosFrac & FRACTIONMASK; - - /* Update the src and dst pointers in case there's still more to do. */ - SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS); - NumSrcSamples -= mini(NumSrcSamples, (DataPosFrac>>FRACTIONBITS)); - - dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize; - pos += DstSize; - } - - *src = SamplesIn; - *srcframes = NumSrcSamples; - - return pos; -} - - -ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans, DevFmtChannels dstChans) -{ - if(srcChans != dstChans && !((srcChans == DevFmtMono && dstChans == DevFmtStereo) || - (srcChans == DevFmtStereo && dstChans == DevFmtMono))) - return nullptr; - return al::make_unique<ChannelConverter>(srcType, srcChans, dstChans); -} - -void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALsizei 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 033e4d3f..00000000 --- a/Alc/converter.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef CONVERTER_H -#define CONVERTER_H - -#include <memory> - -#include "alcmain.h" -#include "alu.h" -#include "almalloc.h" - -struct SampleConverter { - DevFmtType mSrcType{}; - DevFmtType mDstType{}; - ALsizei mSrcTypeSize{}; - ALsizei mDstTypeSize{}; - - ALint mSrcPrepCount{}; - - ALsizei mFracOffset{}; - ALsizei mIncrement{}; - InterpState mState{}; - ResamplerFunc mResample{}; - - alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{}; - alignas(16) ALfloat mDstSamples[BUFFERSIZE]{}; - - struct ChanSamples { - alignas(16) ALfloat PrevSamples[MAX_RESAMPLE_PADDING*2]; - }; - al::FlexArray<ChanSamples> mChan; - - SampleConverter(size_t numchans) : mChan{numchans} { } - SampleConverter(const SampleConverter&) = delete; - SampleConverter& operator=(const SampleConverter&) = delete; - - ALsizei convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes); - ALsizei availableOut(ALsizei srcframes) const; - - static constexpr size_t Sizeof(size_t length) noexcept - { - return maxz(sizeof(SampleConverter), - al::FlexArray<ChanSamples>::Sizeof(length, offsetof(SampleConverter, mChan))); - } - - DEF_PLACE_NEWDEL() -}; -using SampleConverterPtr = std::unique_ptr<SampleConverter>; - -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, ALsizei numchans, - ALsizei srcRate, ALsizei dstRate, Resampler resampler); - - -struct ChannelConverter { - DevFmtType mSrcType; - DevFmtChannels mSrcChans; - DevFmtChannels mDstChans; - - ChannelConverter(DevFmtType srctype, DevFmtChannels srcchans, DevFmtChannels dstchans) - : mSrcType(srctype), mSrcChans(srcchans), mDstChans(dstchans) - { } - - void convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const; - - DEF_NEWDEL(ChannelConverter) -}; -using ChannelConverterPtr = std::unique_ptr<ChannelConverter>; - -ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans, - DevFmtChannels dstChans); - -#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/effects/autowah.cpp b/Alc/effects/autowah.cpp deleted file mode 100644 index 96292636..00000000 --- a/Alc/effects/autowah.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2018 by Raul Herraiz. - * 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 <cmath> -#include <cstdlib> - -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" - -namespace { - -#define MIN_FREQ 20.0f -#define MAX_FREQ 2500.0f -#define Q_FACTOR 5.0f - -struct ALautowahState final : public EffectState { - /* Effect parameters */ - ALfloat mAttackRate; - ALfloat mReleaseRate; - ALfloat mResonanceGain; - ALfloat mPeakGain; - ALfloat mFreqMinNorm; - ALfloat mBandwidthNorm; - ALfloat mEnvDelay; - - /* Filter components derived from the envelope. */ - struct { - ALfloat cos_w0; - ALfloat alpha; - } mEnv[BUFFERSIZE]; - - struct { - /* Effect filters' history. */ - struct { - ALfloat z1, z2; - } Filter; - - /* Effect gains for each output channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]; - } mChans[MAX_AMBI_CHANNELS]; - - /* Effects buffers */ - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; - - - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(ALautowahState) -}; - -ALboolean ALautowahState::deviceUpdate(const ALCdevice*) -{ - /* (Re-)initializing parameters and clear the buffers. */ - - mAttackRate = 1.0f; - mReleaseRate = 1.0f; - mResonanceGain = 10.0f; - mPeakGain = 4.5f; - mFreqMinNorm = 4.5e-4f; - mBandwidthNorm = 0.05f; - mEnvDelay = 0.0f; - - for(auto &e : mEnv) - { - e.cos_w0 = 0.0f; - e.alpha = 0.0f; - } - - 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; - } - - return AL_TRUE; -} - -void ALautowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device{context->Device}; - - const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; - - mAttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency)); - mReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency)); - /* 0-20dB Resonance Peak gain */ - mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f); - mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN); - mFreqMinNorm = MIN_FREQ / device->Frequency; - mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency; - - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } -} - -void ALautowahState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, 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}; - for(ALsizei i{0};i < samplesToDo;i++) - { - ALfloat 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); - - /* 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); - } - mEnvDelay = env_delay; - - ASSUME(numInput > 0); - for(ALsizei c{0};c < numInput;++c) - { - /* 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{mChans[c].Filter.z1}; - ALfloat z2{mChans[c].Filter.z2}; - - for(ALsizei i{0};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]; - - b[0] = 1.0f + alpha*res_gain; - b[1] = -2.0f * cos_w0; - b[2] = 1.0f - alpha*res_gain; - a[0] = 1.0f + alpha/res_gain; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha/res_gain; - - input = samplesIn[c][i]; - output = input*(b[0]/a[0]) + z1; - z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2; - z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); - mBufferOut[i] = output; - } - mChans[c].Filter.z1 = z1; - mChans[c].Filter.z2 = z2; - - /* Now, mix the processed sound data to the output. */ - MixSamples(mBufferOut, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains, - samplesToDo, 0, samplesToDo); - } -} - - -void ALautowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range"); - props->Autowah.AttackTime = val; - break; - - case AL_AUTOWAH_RELEASE_TIME: - if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range"); - props->Autowah.ReleaseTime = val; - break; - - case AL_AUTOWAH_RESONANCE: - if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range"); - props->Autowah.Resonance = val; - break; - - case AL_AUTOWAH_PEAK_GAIN: - if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range"); - props->Autowah.PeakGain = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } -} -void ALautowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ ALautowah_setParamf(props, context, param, vals[0]); } - -void ALautowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void ALautowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -void ALautowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } - -} -void ALautowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ ALautowah_getParamf(props, context, param, vals); } - -void ALautowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void ALautowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(ALautowah); - - -struct AutowahStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ALautowahState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &ALautowah_vtable; } -}; - -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() -{ - static AutowahStateFactory AutowahFactory{}; - return &AutowahFactory; -} diff --git a/Alc/effects/base.h b/Alc/effects/base.h deleted file mode 100644 index 4f48de22..00000000 --- a/Alc/effects/base.h +++ /dev/null @@ -1,196 +0,0 @@ -#ifndef EFFECTS_BASE_H -#define EFFECTS_BASE_H - -#include "alcmain.h" -#include "almalloc.h" -#include "alspan.h" -#include "atomic.h" - - -struct ALeffectslot; - - -union EffectProps { - struct { - // Shared Reverb Properties - ALfloat Density; - ALfloat Diffusion; - ALfloat Gain; - ALfloat GainHF; - ALfloat DecayTime; - ALfloat DecayHFRatio; - ALfloat ReflectionsGain; - ALfloat ReflectionsDelay; - ALfloat LateReverbGain; - ALfloat LateReverbDelay; - ALfloat AirAbsorptionGainHF; - ALfloat RoomRolloffFactor; - ALboolean DecayHFLimit; - - // Additional EAX Reverb Properties - ALfloat GainLF; - ALfloat DecayLFRatio; - ALfloat ReflectionsPan[3]; - ALfloat LateReverbPan[3]; - ALfloat EchoTime; - ALfloat EchoDepth; - ALfloat ModulationTime; - ALfloat ModulationDepth; - ALfloat HFReference; - ALfloat LFReference; - } Reverb; - - struct { - ALfloat AttackTime; - ALfloat ReleaseTime; - ALfloat Resonance; - ALfloat PeakGain; - } Autowah; - - struct { - ALint Waveform; - ALint Phase; - ALfloat Rate; - ALfloat Depth; - ALfloat Feedback; - ALfloat Delay; - } Chorus; /* Also Flanger */ - - struct { - ALboolean OnOff; - } Compressor; - - struct { - ALfloat Edge; - ALfloat Gain; - ALfloat LowpassCutoff; - ALfloat EQCenter; - ALfloat EQBandwidth; - } Distortion; - - struct { - ALfloat Delay; - ALfloat LRDelay; - - ALfloat Damping; - ALfloat Feedback; - - ALfloat Spread; - } Echo; - - struct { - ALfloat LowCutoff; - ALfloat LowGain; - ALfloat Mid1Center; - ALfloat Mid1Gain; - ALfloat Mid1Width; - ALfloat Mid2Center; - ALfloat Mid2Gain; - ALfloat Mid2Width; - ALfloat HighCutoff; - ALfloat HighGain; - } Equalizer; - - struct { - ALfloat Frequency; - ALint LeftDirection; - ALint RightDirection; - } Fshifter; - - struct { - ALfloat Frequency; - ALfloat HighPassCutoff; - ALint Waveform; - } Modulator; - - struct { - ALint CoarseTune; - ALint FineTune; - } Pshifter; - - struct { - ALfloat 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 { - RefCount mRef{1u}; - - 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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) = 0; - - void IncRef() noexcept; - void DecRef() noexcept; -}; - - -struct EffectStateFactory { - virtual ~EffectStateFactory() { } - - virtual EffectState *create() = 0; - virtual EffectProps getDefaultProps() const noexcept = 0; - virtual const EffectVtable *getEffectVtable() const noexcept = 0; -}; - - -EffectStateFactory *NullStateFactory_getFactory(void); -EffectStateFactory *ReverbStateFactory_getFactory(void); -EffectStateFactory *StdReverbStateFactory_getFactory(void); -EffectStateFactory *AutowahStateFactory_getFactory(void); -EffectStateFactory *ChorusStateFactory_getFactory(void); -EffectStateFactory *CompressorStateFactory_getFactory(void); -EffectStateFactory *DistortionStateFactory_getFactory(void); -EffectStateFactory *EchoStateFactory_getFactory(void); -EffectStateFactory *EqualizerStateFactory_getFactory(void); -EffectStateFactory *FlangerStateFactory_getFactory(void); -EffectStateFactory *FshifterStateFactory_getFactory(void); -EffectStateFactory *ModulatorStateFactory_getFactory(void); -EffectStateFactory *PshifterStateFactory_getFactory(void); -EffectStateFactory* VmorpherStateFactory_getFactory(void); - -EffectStateFactory *DedicatedStateFactory_getFactory(void); - - -#endif /* EFFECTS_BASE_H */ diff --git a/Alc/effects/chorus.cpp b/Alc/effects/chorus.cpp deleted file mode 100644 index d475b57a..00000000 --- a/Alc/effects/chorus.cpp +++ /dev/null @@ -1,538 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2013 by Mike Gorchak - * 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 <algorithm> -#include <climits> -#include <cmath> -#include <cstdlib> -#include <iterator> - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/efx.h" - -#include "alAuxEffectSlot.h" -#include "alcmain.h" -#include "alError.h" -#include "alcontext.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alspan.h" -#include "alu.h" -#include "ambidefs.h" -#include "effects/base.h" -#include "math_defs.h" -#include "opthelpers.h" -#include "vector.h" - - -namespace { - -static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); -static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); - -enum class WaveForm { - Sinusoid, - Triangle -}; - -void GetTriangleDelays(ALint *delays, const ALsizei start_offset, const ALsizei lfo_range, - const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const ALsizei todo) -{ - ASSUME(start_offset >= 0); - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - - ALsizei offset{start_offset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint - { - offset = (offset+1)%lfo_range; - return fastf2i((1.0f - std::abs(2.0f - lfo_scale*offset)) * depth) + delay; - }; - std::generate_n(delays, todo, gen_lfo); -} - -void GetSinusoidDelays(ALint *delays, const ALsizei start_offset, const ALsizei lfo_range, - const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const ALsizei todo) -{ - ASSUME(start_offset >= 0); - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - - ALsizei offset{start_offset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint - { - ASSUME(delay >= 0); - offset = (offset+1)%lfo_range; - return fastf2i(std::sin(lfo_scale*offset) * depth) + delay; - }; - std::generate_n(delays, todo, gen_lfo); -} - -struct ChorusState final : public EffectState { - al::vector<ALfloat,16> mSampleBuffer; - ALsizei mOffset{0}; - - ALsizei mLfoOffset{0}; - ALsizei mLfoRange{1}; - ALfloat mLfoScale{0.0f}; - ALint mLfoDisp{0}; - - /* Gains for left and right sides */ - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; - } mGains[2]; - - /* effect parameters */ - WaveForm mWaveform{}; - ALint mDelay{0}; - ALfloat mDepth{0.0f}; - ALfloat mFeedback{0.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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(ChorusState) -}; - -ALboolean ChorusState::deviceUpdate(const ALCdevice *Device) -{ - const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY); - size_t maxlen; - - maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u); - if(maxlen <= 0) return AL_FALSE; - - if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } - - std::fill(mSampleBuffer.begin(), mSampleBuffer.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) -{ - static constexpr ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS; - - switch(props->Chorus.Waveform) - { - case AL_CHORUS_WAVEFORM_TRIANGLE: - mWaveform = WaveForm::Triangle; - break; - case AL_CHORUS_WAVEFORM_SINUSOID: - mWaveform = WaveForm::Sinusoid; - break; - } - - /* The LFO depth is scaled to be relative to the sample delay. Clamp the - * delay and depth to allow enough padding for resampling. - */ - const ALCdevice *device{Context->Device}; - const auto frequency = static_cast<ALfloat>(device->Frequency); - mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay); - mDepth = minf(props->Chorus.Depth * mDelay, static_cast<ALfloat>(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]); - - 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); - - ALfloat rate{props->Chorus.Rate}; - if(!(rate > 0.0f)) - { - mLfoOffset = 0; - mLfoRange = 1; - mLfoScale = 0.0f; - mLfoDisp = 0; - } - else - { - /* Calculate LFO coefficient (number of samples per cycle). Limit the - * max range to avoid overflow when calculating the displacement. - */ - ALsizei lfo_range = float2int(minf(frequency/rate + 0.5f, static_cast<ALfloat>(INT_MAX/360 - 180))); - - mLfoOffset = float2int(static_cast<ALfloat>(mLfoOffset)/mLfoRange*lfo_range + 0.5f) % lfo_range; - mLfoRange = lfo_range; - switch(mWaveform) - { - case WaveForm::Triangle: - mLfoScale = 4.0f / mLfoRange; - break; - case WaveForm::Sinusoid: - mLfoScale = al::MathDefs<float>::Tau() / mLfoRange; - break; - } - - /* Calculate lfo phase displacement */ - ALint phase{props->Chorus.Phase}; - if(phase < 0) phase = 360 + phase; - mLfoDisp = (mLfoRange*phase + 180) / 360; - } -} - -void ChorusState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut) -{ - const auto bufmask = static_cast<ALsizei>(mSampleBuffer.size()-1); - const ALfloat feedback{mFeedback}; - const ALsizei avgdelay{(mDelay + (FRACTIONONE>>1)) >> FRACTIONBITS}; - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; - ALsizei offset{mOffset}; - - for(ALsizei base{0};base < samplesToDo;) - { - const ALsizei todo = mini(256, samplesToDo-base); - ALint moddelays[2][256]; - alignas(16) ALfloat temps[2][256]; - - if(mWaveform == WaveForm::Sinusoid) - { - GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - else /*if(mWaveform == WaveForm::Triangle)*/ - { - GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - mLfoOffset = (mLfoOffset+todo) % mLfoRange; - - for(ALsizei i{0};i < todo;i++) - { - // Feed the buffer's input first (necessary for delays < 1). - delaybuf[offset&bufmask] = samplesIn[0][base+i]; - - // Tap for the left output. - ALint delay{offset - (moddelays[0][i]>>FRACTIONBITS)}; - ALfloat mu{(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)}; - temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], - mu); - - // Tap for the right output. - delay = offset - (moddelays[1][i]>>FRACTIONBITS); - mu = (moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE); - temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], - mu); - - // Accumulate feedback from the average delay of the taps. - delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; - offset++; - } - - for(ALsizei c{0};c < 2;c++) - MixSamples(temps[c], samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo-base, - base, todo); - - base += todo; - } - - mOffset = offset; -} - - -void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - 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; - - 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; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); - } -} -void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Chorus_setParami(props, context, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - 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; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); - } -} -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) - { - case AL_CHORUS_WAVEFORM: - *val = props->Chorus.Waveform; - break; - - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); - } -} -void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Chorus_getParami(props, context, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - 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: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); - } -} -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 -{ - 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; -} - - -void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - 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; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); - } -} -void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Flanger_setParami(props, context, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_FLANGER_RATE: - if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range"); - props->Chorus.Rate = val; - break; - - case AL_FLANGER_DEPTH: - if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range"); - props->Chorus.Depth = val; - break; - - case AL_FLANGER_FEEDBACK: - if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range"); - props->Chorus.Feedback = val; - break; - - 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: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); - } -} -void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Flanger_setParamf(props, context, param, vals[0]); } - -void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_FLANGER_WAVEFORM: - *val = props->Chorus.Waveform; - break; - - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); - } -} -void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Flanger_getParami(props, context, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); - } -} -void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Flanger_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Flanger); - - -/* 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; } -}; - -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() -{ - static ChorusStateFactory ChorusFactory{}; - return &ChorusFactory; -} - -EffectStateFactory *FlangerStateFactory_getFactory() -{ - static FlangerStateFactory FlangerFactory{}; - return &FlangerFactory; -} diff --git a/Alc/effects/compressor.cpp b/Alc/effects/compressor.cpp deleted file mode 100644 index 4a487097..00000000 --- a/Alc/effects/compressor.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/** - * OpenAL 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. - * - * 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 <cstdlib> - -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "vecmat.h" - - -namespace { - -#define AMP_ENVELOPE_MIN 0.5f -#define AMP_ENVELOPE_MAX 2.0f - -#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */ -#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */ - - -struct CompressorState final : public EffectState { - /* Effect gains for each channel */ - ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{}; - - /* Effect parameters */ - ALboolean mEnabled{AL_TRUE}; - ALfloat mAttackMult{1.0f}; - ALfloat mReleaseMult{1.0f}; - ALfloat 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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(CompressorState) -}; - -ALboolean CompressorState::deviceUpdate(const ALCdevice *device) -{ - /* 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; - - /* 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) -{ - mEnabled = props->Compressor.OnOff; - - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]); - } -} - -void CompressorState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) -{ - for(ALsizei base{0};base < samplesToDo;) - { - ALfloat gains[256]; - const ALsizei td{mini(256, samplesToDo-base)}; - - /* Generate the per-sample gains from the signal envelope. */ - ALfloat env{mEnvFollower}; - if(mEnabled) - { - for(ALsizei i{0};i < td;++i) - { - /* Clamp the absolute amplitude to the defined envelope limits, - * then attack or release the envelope to reach it. - */ - const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, - AMP_ENVELOPE_MAX)}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); - - /* Apply the reciprocal of the envelope to normalize the volume - * (compress the dynamic range). - */ - gains[i] = 1.0f / env; - } - } - else - { - /* Same as above, except the amplitude is forced to 1. This helps - * ensure smooth gain changes when the compressor is turned on and - * off. - */ - for(ALsizei i{0};i < td;++i) - { - const ALfloat amplitude{1.0f}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); - - gains[i] = 1.0f / env; - } - } - mEnvFollower = env; - - /* Now compress the signal amplitude to output. */ - ASSUME(numInput > 0); - for(ALsizei j{0};j < numInput;j++) - { - const ALfloat *outgains{mGain[j]}; - for(FloatBufferLine &output : samplesOut) - { - const ALfloat gain{*(outgains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - for(ALsizei i{0};i < td;i++) - output[base+i] += samplesIn[j][base+i] * gains[i] * gain; - } - } - - base += td; - } -} - - -void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range"); - props->Compressor.OnOff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Compressor_setParami(props, context, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Compressor_getParami(props, context, param, vals); } -void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Compressor); - - -struct CompressorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new CompressorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; } -}; - -EffectProps CompressorStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - -} // namespace - -EffectStateFactory *CompressorStateFactory_getFactory() -{ - static CompressorStateFactory CompressorFactory{}; - return &CompressorFactory; -} diff --git a/Alc/effects/dedicated.cpp b/Alc/effects/dedicated.cpp deleted file mode 100644 index b31b3750..00000000 --- a/Alc/effects/dedicated.cpp +++ /dev/null @@ -1,159 +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 <cstdlib> -#include <cmath> -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" - - -namespace { - -struct DedicatedState final : public EffectState { - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat 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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(DedicatedState) -}; - -ALboolean DedicatedState::deviceUpdate(const ALCdevice*) -{ - 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) -{ - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - - const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain}; - - if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT) - { - const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, LFE)}; - if(idx != -1) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } - } - else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE) - { - /* Dialog goes to the front-center speaker if it exists, otherwise it - * plays from the front-center location. */ - const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, FrontCenter)}; - if(idx != -1) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } - else - { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); - - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, Gain, mTargetGains); - } - } -} - -void DedicatedState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut) -{ - MixSamples(samplesIn[0].data(), samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0, - samplesToDo); -} - - -void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - if(!(val >= 0.0f && std::isfinite(val))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range"); - props->Dedicated.Gain = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Dedicated_setParamf(props, context, param, vals[0]); } - -void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Dedicated_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Dedicated); - - -struct DedicatedStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DedicatedState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; } -}; - -EffectProps DedicatedStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; -} - -} // namespace - -EffectStateFactory *DedicatedStateFactory_getFactory() -{ - static DedicatedStateFactory DedicatedFactory{}; - return &DedicatedFactory; -} diff --git a/Alc/effects/distortion.cpp b/Alc/effects/distortion.cpp deleted file mode 100644 index 59557395..00000000 --- a/Alc/effects/distortion.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2013 by Mike Gorchak - * 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 <cmath> -#include <cstdlib> - -#include <cmath> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" - - -namespace { - -struct DistortionState final : public EffectState { - /* Effect gains for each channel */ - ALfloat mGain[MAX_OUTPUT_CHANNELS]{}; - - /* Effect parameters */ - BiquadFilter mLowpass; - BiquadFilter mBandpass; - ALfloat mAttenuation{}; - ALfloat mEdgeCoeff{}; - - ALfloat mBuffer[2][BUFFERSIZE]{}; - - - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(DistortionState) -}; - -ALboolean DistortionState::deviceUpdate(const ALCdevice*) -{ - mLowpass.clear(); - mBandpass.clear(); - return AL_TRUE; -} - -void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device{context->Device}; - - /* Store waveshaper edge settings. */ - const ALfloat edge{ - minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)}; - mEdgeCoeff = 2.0f * edge / (1.0f-edge); - - ALfloat 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 - * 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)); - - 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)); - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); - - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain); -} - -void DistortionState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut) -{ - const ALfloat fc{mEdgeCoeff}; - for(ALsizei base{0};base < samplesToDo;) - { - /* Perform 4x oversampling to avoid aliasing. Oversampling greatly - * improves distortion quality and allows to implement lowpass and - * bandpass filters using high frequencies, at which classic IIR - * filters became unstable. - */ - ALsizei todo{mini(BUFFERSIZE, (samplesToDo-base) * 4)}; - - /* Fill oversample buffer using zero stuffing. Multiply the sample by - * the amount of oversampling to maintain the signal's power. - */ - for(ALsizei i{0};i < todo;i++) - mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f; - - /* First step, do lowpass filtering of original signal. Additionally - * perform buffer interpolation and lowpass cutoff for oversampling - * (which is fortunately first step of distortion). So combine three - * operations into the one. - */ - mLowpass.process(mBuffer[1], mBuffer[0], todo); - - /* 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(ALsizei i{0};i < todo;i++) - { - 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; - } - - /* Third step, do bandpass filtering of distorted signal. */ - mBandpass.process(mBuffer[1], mBuffer[0], todo); - - todo >>= 2; - const ALfloat *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)) - continue; - - for(ALsizei i{0};i < todo;i++) - output[base+i] += gain * mBuffer[1][i*4]; - } - - base += todo; - } -} - - -void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range"); - props->Distortion.Edge = val; - break; - - case AL_DISTORTION_GAIN: - if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range"); - props->Distortion.Gain = val; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range"); - props->Distortion.LowpassCutoff = val; - break; - - case AL_DISTORTION_EQCENTER: - if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range"); - props->Distortion.EQCenter = val; - break; - - case AL_DISTORTION_EQBANDWIDTH: - if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range"); - props->Distortion.EQBandwidth = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", - param); - } -} -void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Distortion_setParamf(props, context, param, vals[0]); } - -void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", - param); - } -} -void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Distortion_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Distortion); - - -struct DistortionStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DistortionState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; } -}; - -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() -{ - static DistortionStateFactory DistortionFactory{}; - return &DistortionFactory; -} diff --git a/Alc/effects/echo.cpp b/Alc/effects/echo.cpp deleted file mode 100644 index c10f2eb2..00000000 --- a/Alc/effects/echo.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2009 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 <cmath> -#include <cstdlib> - -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alFilter.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vector.h" - - -namespace { - -struct EchoState final : public EffectState { - al::vector<ALfloat,16> mSampleBuffer; - - // The echo is two tap. The delay is the number of samples from before the - // current offset - struct { - ALsizei delay{0}; - } mTap[2]; - ALsizei mOffset{0}; - - /* The panning gains for the two taps */ - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; - } mGains[2]; - - BiquadFilter mFilter; - ALfloat mFeedGain{0.0f}; - - alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE]; - - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(EchoState) -}; - -ALboolean EchoState::deviceUpdate(const ALCdevice *Device) -{ - ALuint maxlen; - - // Use the next power of 2 for the buffer length, so the tap offsets can be - // wrapped using a mask instead of a modulo - maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) + - float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f); - maxlen = NextPowerOf2(maxlen); - if(maxlen <= 0) return AL_FALSE; - - if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } - - std::fill(mSampleBuffer.begin(), mSampleBuffer.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 EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device = context->Device; - const auto frequency = static_cast<ALfloat>(device->Frequency); - - mTap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1); - mTap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay; - - 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)); - - mFeedGain = props->Echo.Feedback; - - /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */ - const ALfloat 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]); - - 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); -} - -void EchoState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut) -{ - const auto mask = static_cast<ALsizei>(mSampleBuffer.size()-1); - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; - ALsizei offset{mOffset}; - ALsizei tap1{offset - mTap[0].delay}; - ALsizei tap2{offset - mTap[1].delay}; - ALfloat z1, z2; - - ASSUME(samplesToDo > 0); - ASSUME(mask > 0); - - std::tie(z1, z2) = mFilter.getComponents(); - for(ALsizei i{0};i < samplesToDo;) - { - offset &= mask; - tap1 &= mask; - tap2 &= mask; - - ALsizei td{mini(mask+1 - maxi(offset, maxi(tap1, tap2)), samplesToDo-i)}; - do { - /* Feed the delay buffer's input first. */ - delaybuf[offset] = samplesIn[0][i]; - - /* Get delayed output from the first and second taps. Use the - * second tap for feedback. - */ - mTempBuffer[0][i] = delaybuf[tap1++]; - mTempBuffer[1][i] = delaybuf[tap2++]; - const float feedb{mTempBuffer[1][i++]}; - - /* Add feedback to the delay buffer with damping and attenuation. */ - delaybuf[offset++] += mFilter.processOne(feedb, z1, z2) * mFeedGain; - } while(--td); - } - mFilter.setComponents(z1, z2); - mOffset = offset; - - for(ALsizei c{0};c < 2;c++) - MixSamples(mTempBuffer[c], samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo, 0, - samplesToDo); -} - - -void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_ECHO_DELAY: - if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range"); - props->Echo.Delay = val; - break; - - case AL_ECHO_LRDELAY: - if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range"); - props->Echo.LRDelay = val; - break; - - case AL_ECHO_DAMPING: - if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range"); - props->Echo.Damping = val; - break; - - case AL_ECHO_FEEDBACK: - if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range"); - props->Echo.Feedback = val; - break; - - case AL_ECHO_SPREAD: - if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range"); - props->Echo.Spread = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Echo_setParamf(props, context, param, vals[0]); } - -void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Echo_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Echo); - - -struct EchoStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EchoState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; } -}; - -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() -{ - static EchoStateFactory EchoFactory{}; - return &EchoFactory; -} diff --git a/Alc/effects/equalizer.cpp b/Alc/effects/equalizer.cpp deleted file mode 100644 index 69ab5021..00000000 --- a/Alc/effects/equalizer.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2013 by Mike Gorchak - * 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 <cmath> -#include <cstdlib> - -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" - - -namespace { - -/* The document "Effects Extension Guide.pdf" says that low and high * - * frequencies are cutoff frequencies. This is not fully correct, they * - * are corner frequencies for low and high shelf filters. If they were * - * just cutoff frequencies, there would be no need in cutoff frequency * - * gains, which are present. Documentation for "Creative Proteus X2" * - * software describes 4-band equalizer functionality in a much better * - * way. This equalizer seems to be a predecessor of OpenAL 4-band * - * equalizer. With low and high shelf filters we are able to cutoff * - * frequencies below and/or above corner frequencies using attenuation * - * gains (below 1.0) and amplify all low and/or high frequencies using * - * gains above 1.0. * - * * - * Low-shelf Low Mid Band High Mid Band High-shelf * - * corner center center corner * - * frequency frequency frequency frequency * - * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz * - * * - * | | | | * - * | | | | * - * B -----+ /--+--\ /--+--\ +----- * - * O |\ | | | | | | /| * - * O | \ - | - - | - / | * - * S + | \ | | | | | | / | * - * T | | | | | | | | | | * - * ---------+---------------+------------------+---------------+-------- * - * C | | | | | | | | | | * - * U - | / | | | | | | \ | * - * T | / - | - - | - \ | * - * O |/ | | | | | | \| * - * F -----+ \--+--/ \--+--/ +----- * - * F | | | | * - * | | | | * - * * - * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation * - * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in * - * octaves for two mid bands. * - * * - * 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 */ - - -struct EqualizerState final : public EffectState { - struct { - /* Effect parameters */ - BiquadFilter filter[4]; - - /* Effect gains for each channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; - - ALfloat mSampleBuffer[BUFFERSIZE]{}; - - - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(EqualizerState) -}; - -ALboolean EqualizerState::deviceUpdate(const ALCdevice*) -{ - 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); - } - return AL_TRUE; -} - -void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device = context->Device; - auto frequency = static_cast<ALfloat>(device->Frequency); - ALfloat gain, f0norm; - - /* Calculate coefficients for the each type of filter. Note that the shelf - * filters' gain is for the reference frequency, which is the centerpoint - * of the transition band. - */ - gain = maxf(sqrtf(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 = maxf(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 = maxf(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 = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f); - f0norm = props->Equalizer.HighCutoff/frequency; - mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm, - BiquadFilter::rcpQFromSlope(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]); - } - - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } -} - -void EqualizerState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) -{ - ASSUME(numInput > 0); - for(ALsizei c{0};c < numInput;c++) - { - mChans[c].filter[0].process(mSampleBuffer, samplesIn[c].data(), samplesToDo); - mChans[c].filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo); - mChans[c].filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo); - mChans[c].filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo); - - MixSamples(mSampleBuffer, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains, - samplesToDo, 0, samplesToDo); - } -} - - -void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range"); - props->Equalizer.LowGain = val; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range"); - props->Equalizer.LowCutoff = val; - break; - - case AL_EQUALIZER_MID1_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range"); - props->Equalizer.Mid1Gain = val; - break; - - case AL_EQUALIZER_MID1_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range"); - props->Equalizer.Mid1Center = val; - break; - - case AL_EQUALIZER_MID1_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range"); - props->Equalizer.Mid1Width = val; - break; - - case AL_EQUALIZER_MID2_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range"); - props->Equalizer.Mid2Gain = val; - break; - - case AL_EQUALIZER_MID2_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range"); - props->Equalizer.Mid2Center = val; - break; - - case AL_EQUALIZER_MID2_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range"); - props->Equalizer.Mid2Width = val; - break; - - case AL_EQUALIZER_HIGH_GAIN: - if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range"); - props->Equalizer.HighGain = val; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range"); - props->Equalizer.HighCutoff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); - } -} -void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Equalizer_setParamf(props, context, param, vals[0]); } - -void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); - } -} -void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Equalizer_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Equalizer); - - -struct EqualizerStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EqualizerState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; } -}; - -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() -{ - static EqualizerStateFactory EqualizerFactory{}; - return &EqualizerFactory; -} diff --git a/Alc/effects/fshifter.cpp b/Alc/effects/fshifter.cpp deleted file mode 100644 index b47aa00e..00000000 --- a/Alc/effects/fshifter.cpp +++ /dev/null @@ -1,301 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2018 by Raul Herraiz. - * 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 <cmath> -#include <cstdlib> -#include <array> -#include <complex> -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" - -#include "alcomplex.h" - -namespace { - -using complex_d = std::complex<double>; - -#define HIL_SIZE 1024 -#define OVERSAMP (1<<2) - -#define HIL_STEP (HIL_SIZE / OVERSAMP) -#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1)) - -/* Define a Hann window, used to filter the HIL input and output. */ -/* Making this constexpr seems to require C++14. */ -std::array<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(ALsizei i{0};i < HIL_SIZE>>1;i++) - { - ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{HIL_SIZE-1}); - ret[i] = ret[HIL_SIZE-1-i] = val * val; - } - return ret; -} -alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow(); - - -struct FshifterState final : public EffectState { - /* Effect parameters */ - ALsizei mCount{}; - ALsizei mPhaseStep{}; - ALsizei mPhase{}; - ALdouble mLdSign{}; - - /*Effects buffers*/ - ALfloat mInFIFO[HIL_SIZE]{}; - complex_d mOutFIFO[HIL_SIZE]{}; - complex_d mOutputAccum[HIL_SIZE]{}; - complex_d mAnalytic[HIL_SIZE]{}; - complex_d mOutdata[BUFFERSIZE]{}; - - alignas(16) ALfloat mBufferOut[BUFFERSIZE]{}; - - /* Effect gains for each output channel */ - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat 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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(FshifterState) -}; - -ALboolean FshifterState::deviceUpdate(const ALCdevice*) -{ - /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; - mPhaseStep = 0; - mPhase = 0; - mLdSign = 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{}); - - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - - return AL_TRUE; -} - -void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device{context->Device}; - - ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)}; - mPhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE); - - switch(props->Fshifter.LeftDirection) - { - case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: - mLdSign = -1.0; - break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_UP: - mLdSign = 1.0; - break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: - mPhase = 0; - mPhaseStep = 0; - break; - } - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); - - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains); -} - -void FshifterState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut) -{ - static constexpr complex_d complex_zero{0.0, 0.0}; - ALfloat *RESTRICT BufferOut = mBufferOut; - ALsizei j, k, base; - - for(base = 0;base < samplesToDo;) - { - const ALsizei todo{mini(HIL_SIZE-mCount, samplesToDo-base)}; - - ASSUME(todo > 0); - - /* 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; - - /* Check whether FIFO buffer is filled */ - if(mCount < HIL_SIZE) continue; - mCount = FIFO_LATENCY; - - /* 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); - } - - /* 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]; - } - - /* Process frequency shifter using the analytic signal obtained. */ - for(k = 0;k < samplesToDo;k++) - { - double phase = mPhase * ((1.0/FRACTIONONE) * al::MathDefs<double>::Tau()); - BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) + - mOutdata[k].imag()*std::sin(phase)*mLdSign); - - mPhase += mPhaseStep; - mPhase &= FRACTIONMASK; - } - - /* Now, mix the processed sound data to the output. */ - MixSamples(BufferOut, samplesOut, mCurrentGains, mTargetGains, maxi(samplesToDo, 512), 0, - samplesToDo); -} - - -void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range"); - props->Fshifter.Frequency = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param); - } -} -void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Fshifter_setParamf(props, context, param, vals[0]); } - -void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range"); - props->Fshifter.LeftDirection = val; - break; - - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range"); - props->Fshifter.RightDirection = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param); - } -} -void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Fshifter_setParami(props, context, param, vals[0]); } - -void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - *val = props->Fshifter.LeftDirection; - break; - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - *val = props->Fshifter.RightDirection; - break; - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param); - } -} -void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Fshifter_getParami(props, context, param, vals); } - -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: - alSetError(context, 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; } -}; - -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() -{ - static FshifterStateFactory FshifterFactory{}; - return &FshifterFactory; -} diff --git a/Alc/effects/modulator.cpp b/Alc/effects/modulator.cpp deleted file mode 100644 index 086482d7..00000000 --- a/Alc/effects/modulator.cpp +++ /dev/null @@ -1,279 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2009 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 <cmath> -#include <cstdlib> - -#include <cmath> -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" - - -namespace { - -#define MAX_UPDATE_SAMPLES 128 - -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS) -#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1) - -inline ALfloat Sin(ALsizei index) -{ - return std::sin(static_cast<ALfloat>(index) * - (al::MathDefs<float>::Tau() / ALfloat{WAVEFORM_FRACONE})); -} - -inline ALfloat Saw(ALsizei index) -{ - return static_cast<ALfloat>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; -} - -inline ALfloat Square(ALsizei index) -{ - return static_cast<ALfloat>(((index>>(WAVEFORM_FRACBITS-2))&2) - 1); -} - -inline ALfloat One(ALsizei) -{ - return 1.0f; -} - -template<ALfloat func(ALsizei)> -void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo) -{ - ALsizei i; - for(i = 0;i < todo;i++) - { - index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } -} - - -struct ModulatorState final : public EffectState { - void (*mGetSamples)(ALfloat*RESTRICT, ALsizei, const ALsizei, ALsizei){}; - - ALsizei mIndex{0}; - ALsizei mStep{1}; - - struct { - BiquadFilter Filter; - - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(ModulatorState) -}; - -ALboolean ModulatorState::deviceUpdate(const ALCdevice*) -{ - for(auto &e : mChans) - { - e.Filter.clear(); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); - } - return AL_TRUE; -} - -void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device{context->Device}; - - const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)}; - mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1})); - - if(mStep == 0) - mGetSamples = Modulate<One>; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID) - mGetSamples = Modulate<Sin>; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH) - mGetSamples = Modulate<Saw>; - else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/ - mGetSamples = Modulate<Square>; - - ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(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)); - for(size_t i{1u};i < slot->Wet.Buffer.size();++i) - mChans[i].Filter.copyParamsFrom(mChans[0].Filter); - - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } -} - -void ModulatorState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) -{ - for(ALsizei base{0};base < samplesToDo;) - { - alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES]; - ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base); - ALsizei c, i; - - mGetSamples(modsamples, mIndex, mStep, td); - mIndex += (mStep*td) & WAVEFORM_FRACMASK; - mIndex &= WAVEFORM_FRACMASK; - - ASSUME(numInput > 0); - for(c = 0;c < numInput;c++) - { - alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES]; - - mChans[c].Filter.process(temps, &samplesIn[c][base], td); - for(i = 0;i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples(temps, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains, - samplesToDo-base, base, td); - } - - base += td; - } -} - - -void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range"); - props->Modulator.Frequency = val; - break; - - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range"); - props->Modulator.HighPassCutoff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Modulator_setParamf(props, context, param, vals[0]); } -void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, context, param, static_cast<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: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Modulator_setParami(props, context, param, vals[0]); } - -void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast<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: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Modulator_getParami(props, context, param, vals); } -void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = props->Modulator.Frequency; - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = props->Modulator.HighPassCutoff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Modulator_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Modulator); - - -struct ModulatorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ModulatorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; } -}; - -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() -{ - static ModulatorStateFactory ModulatorFactory{}; - return &ModulatorFactory; -} diff --git a/Alc/effects/null.cpp b/Alc/effects/null.cpp deleted file mode 100644 index e55c8699..00000000 --- a/Alc/effects/null.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "config.h" - -#include <cstdlib> - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" - - -namespace { - -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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(NullState) -}; - -/* This constructs the effect state. It's called when the object is first - * created. - */ -NullState::NullState() = default; - -/* This destructs the effect state. It's called only when the effect instance - * is no longer used. - */ -NullState::~NullState() = default; - -/* This updates the device-dependant effect state. This is called on state - * initialization and any time the device parameters (e.g. playback frequency, - * format) have been changed. Will always be followed by a call to the update - * method, if successful. - */ -ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/) -{ - 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*/, - const EffectProps* /*props*/, const EffectTarget /*target*/) -{ -} - -/* This processes the effect state, for the given number of samples from the - * input to the output buffer. The result should be added to the output buffer, - * not replace it. - */ -void NullState::process(const ALsizei /*samplesToDo*/, - const FloatBufferLine *RESTRICT /*samplesIn*/, const ALsizei /*numInput*/, - const al::span<FloatBufferLine> /*samplesOut*/) -{ -} - - -void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ - switch(param) - { - default: - NullEffect_setParami(props, context, param, vals[0]); - } -} -void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) - { - default: - NullEffect_setParamf(props, context, param, vals[0]); - } -} - -void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ - switch(param) - { - default: - NullEffect_getParami(props, context, param, vals); - } -} -void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ - 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; -}; - -/* 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; } - -} // namespace - -EffectStateFactory *NullStateFactory_getFactory() -{ - static NullStateFactory NullFactory{}; - return &NullFactory; -} diff --git a/Alc/effects/pshifter.cpp b/Alc/effects/pshifter.cpp deleted file mode 100644 index 39d3cf1a..00000000 --- a/Alc/effects/pshifter.cpp +++ /dev/null @@ -1,405 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2018 by Raul Herraiz. - * 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" - -#ifdef HAVE_SSE_INTRINSICS -#include <emmintrin.h> -#endif - -#include <cmath> -#include <cstdlib> -#include <array> -#include <complex> -#include <algorithm> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" - -#include "alcomplex.h" - - -namespace { - -using complex_d = std::complex<double>; - -#define STFT_SIZE 1024 -#define STFT_HALF_SIZE (STFT_SIZE>>1) -#define OVERSAMP (1<<2) - -#define STFT_STEP (STFT_SIZE / OVERSAMP) -#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1)) - -inline int double2int(double d) -{ -#if defined(HAVE_SSE_INTRINSICS) - return _mm_cvttsd_si32(_mm_set_sd(d)); - -#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \ - !defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) - - int sign, shift; - int64_t mant; - union { - double d; - int64_t i64; - } conv; - - conv.d = d; - sign = (conv.i64>>63) | 1; - shift = ((conv.i64>>52)&0x7ff) - (1023+52); - - /* Over/underflow */ - if(UNLIKELY(shift >= 63 || shift < -52)) - return 0; - - mant = (conv.i64&0xfffffffffffff_i64) | 0x10000000000000_i64; - if(LIKELY(shift < 0)) - return (int)(mant >> -shift) * sign; - return (int)(mant << shift) * sign; - -#else - - return static_cast<int>(d); -#endif -} - -/* 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(ALsizei i{0};i < STFT_SIZE>>1;i++) - { - ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{STFT_SIZE-1}); - ret[i] = ret[STFT_SIZE-1-i] = val * val; - } - return ret; -} -alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow(); - - -struct ALphasor { - ALdouble Amplitude; - ALdouble Phase; -}; - -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 PshifterState final : public EffectState { - /* Effect parameters */ - ALsizei mCount; - ALsizei mPitchShiftI; - ALfloat mPitchShift; - ALfloat mFreqPerBin; - - /* 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]; - - complex_d mFFTbuffer[STFT_SIZE]; - - ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1]; - ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1]; - - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; - - /* Effect gains for each output channel */ - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat 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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(PshifterState) -}; - -ALboolean PshifterState::deviceUpdate(const ALCdevice *device) -{ - /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; - mPitchShiftI = FRACTIONONE; - mPitchShift = 1.0f; - mFreqPerBin = device->Frequency / static_cast<ALfloat>(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{}); - - 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) -{ - const float pitch{std::pow(2.0f, - static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f - )}; - mPitchShiftI = fastf2i(pitch*FRACTIONONE); - mPitchShift = mPitchShiftI * (1.0f/FRACTIONONE); - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); - - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains); -} - -void PshifterState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, 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}; - ALsizei count{mCount}; - - for(ALsizei i{0};i < samplesToDo;) - { - do { - /* Fill FIFO buffer with samples data */ - mInFIFO[count] = samplesIn[0][i]; - bufferOut[i] = mOutFIFO[count - FIFO_LATENCY]; - - count++; - } while(++i < samplesToDo && count < STFT_SIZE); - - /* Check whether FIFO buffer is filled */ - if(count < STFT_SIZE) break; - count = FIFO_LATENCY; - - /* Real signal windowing and store in FFTbuffer */ - for(ALsizei k{0};k < STFT_SIZE;k++) - { - mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]); - mFFTbuffer[k].imag(0.0); - } - - /* ANALYSIS */ - /* Apply FFT to FFTbuffer data */ - complex_fft(mFFTbuffer, -1.0); - - /* Analyze the obtained data. Since the real FFT is symmetric, only - * STFT_HALF_SIZE+1 samples are needed. - */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) - { - /* 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. - */ - mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude; - mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin; - - /* Store actual phase[k] for the calculations in the next frame*/ - mLastPhase[k] = component.Phase; - } - - /* PROCESSING */ - /* pitch shifting */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) - { - mSyntesis_buffer[k].Amplitude = 0.0; - mSyntesis_buffer[k].Frequency = 0.0; - } - - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) - { - ALsizei j{(k*mPitchShiftI) >> FRACTIONBITS}; - if(j >= STFT_HALF_SIZE+1) break; - - mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude; - mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift; - } - - /* SYNTHESIS */ - /* Synthesis the processing data */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) - { - ALphasor component; - ALdouble tmp; - - /* Compute bin deviation from scaled freq */ - tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k; - - /* Calculate actual delta phase and accumulate it to get bin phase */ - mSumPhase[k] += (k + tmp) * expected; - - component.Amplitude = mSyntesis_buffer[k].Amplitude; - component.Phase = mSumPhase[k]; - - /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/ - mFFTbuffer[k] = polar2rect(component); - } - /* zero negative frequencies for recontruct a real signal */ - for(ALsizei k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++) - mFFTbuffer[k] = complex_d{}; - - /* Apply iFFT to buffer data */ - complex_fft(mFFTbuffer, 1.0); - - /* Windowing and add to output */ - for(ALsizei k{0};k < STFT_SIZE;k++) - mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() / - (0.5 * STFT_HALF_SIZE * OVERSAMP); - - /* Shift accumulator, input & output FIFO */ - ALsizei j, k; - for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<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, samplesOut, mCurrentGains, mTargetGains, maxi(samplesToDo, 512), 0, - samplesToDo); -} - - -void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); } - -void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range"); - props->Pshifter.CoarseTune = val; - break; - - case AL_PITCH_SHIFTER_FINE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range"); - props->Pshifter.FineTune = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param); - } -} -void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Pshifter_setParami(props, context, param, vals[0]); } - -void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param); - } -} -void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Pshifter_getParami(props, context, param, vals); } - -void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Pshifter); - - -struct PshifterStateFactory final : public EffectStateFactory { - EffectState *create() override; - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; } -}; - -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() -{ - static PshifterStateFactory PshifterFactory{}; - return &PshifterFactory; -} diff --git a/Alc/effects/reverb.cpp b/Alc/effects/reverb.cpp deleted file mode 100644 index ac996b3f..00000000 --- a/Alc/effects/reverb.cpp +++ /dev/null @@ -1,2102 +0,0 @@ -/** - * Ambisonic reverb engine for the OpenAL cross platform audio library - * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include <cstdio> -#include <cstdlib> -#include <cmath> - -#include <array> -#include <numeric> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "alAuxEffectSlot.h" -#include "alListener.h" -#include "alError.h" -#include "bformatdec.h" -#include "filters/biquad.h" -#include "vector.h" -#include "vecmat.h" - -/* This is a user config option for modifying the overall output of the reverb - * effect. - */ -ALfloat ReverbBoost = 1.0f; - -namespace { - -using namespace std::placeholders; - -/* The number of samples used for cross-faded delay lines. This can be used - * to balance the compensation for abrupt line changes and attenuation due to - * minimally lengthed recursive lines. Try to keep this below the device - * update size. - */ -constexpr int FADE_SAMPLES{128}; - -/* The number of spatialized lines or channels to process. Four channels allows - * for a 3D A-Format response. NOTE: This can't be changed without taking care - * of the conversion matrices, and a few places where the length arrays are - * assumed to have 4 elements. - */ -constexpr int NUM_LINES{4}; - - -/* 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 - * back left). It's not quite opposite, since the A-Format results in a - * 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 } -}; - -/* 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 } -}; - - -constexpr ALfloat FadeStep{1.0f / FADE_SAMPLES}; - -/* The all-pass and delay lines have a variable length dependent on the - * effect's density parameter, which helps alter the perceived environment - * size. The size-to-density conversion is a cubed scale: - * - * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE); - * - * The line lengths scale linearly with room size, so the inverse density - * conversion is needed, taking the cube root of the re-scaled density to - * calculate the line length multiplier: - * - * length_mult = max(5.0, cbrt(density*DENSITY_SCALE)); - * - * 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}; - -/* All delay line lengths are specified in seconds. - * - * To approximate early reflections, we break them up into primary (those - * arriving from the same direction as the source) and secondary (those - * arriving from the opposite direction). - * - * The early taps decorrelate the 4-channel signal to approximate an average - * room response for the primary reflections after the initial early delay. - * - * Given an average room dimension (d_a) and the speed of sound (c) we can - * calculate the average reflection delay (r_a) regardless of listener and - * source positions as: - * - * r_a = d_a / c - * c = 343.3 - * - * This can extended to finding the average difference (r_d) between the - * maximum (r_1) and minimum (r_0) reflection delays: - * - * r_0 = 2 / 3 r_a - * = r_a - r_d / 2 - * = r_d - * r_1 = 4 / 3 r_a - * = r_a + r_d / 2 - * = 2 r_d - * r_d = 2 / 3 r_a - * = r_1 - r_0 - * - * As can be determined by integrating the 1D model with a source (s) and - * listener (l) positioned across the dimension of length (d_a): - * - * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c - * - * The initial taps (T_(i=0)^N) are then specified by taking a power series - * that ranges between r_0 and half of r_1 less r_0: - * - * R_i = 2^(i / (2 N - 1)) r_d - * = r_0 + (2^(i / (2 N - 1)) - 1) r_d - * = r_0 + T_i - * T_i = R_i - r_0 - * = (2^(i / (2 N - 1)) - 1) r_d - * - * Assuming an average of 1m, we get the following taps: - */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_TAP_LENGTHS{{ - 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f -}}; - -/* The early all-pass filter lengths are based on the early tap lengths: - * - * A_i = R_i / a - * - * Where a is the approximate maximum all-pass cycle limit (20). - */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_ALLPASS_LENGTHS{{ - 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f -}}; - -/* The early delay lines are used to transform the primary reflections into - * the secondary reflections. The A-format is arranged in such a way that - * the channels/lines are spatially opposite: - * - * C_i is opposite C_(N-i-1) - * - * The delays of the two opposing reflections (R_i and O_i) from a source - * anywhere along a particular dimension always sum to twice its full delay: - * - * 2 r_a = R_i + O_i - * - * With that in mind we can determine the delay between the two reflections - * and thus specify our early line lengths (L_(i=0)^N) using: - * - * O_i = 2 r_a - R_(N-i-1) - * L_i = O_i - R_(N-i-1) - * = 2 (r_a - R_(N-i-1)) - * = 2 (r_a - T_(N-i-1) - r_0) - * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1))) - * - * Using an average dimension of 1m, we get: - */ -constexpr std::array<ALfloat,NUM_LINES> EARLY_LINE_LENGTHS{{ - 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f -}}; - -/* The late all-pass filter lengths are based on the late line lengths: - * - * A_i = (5 / 3) L_i / r_1 - */ -constexpr std::array<ALfloat,NUM_LINES> LATE_ALLPASS_LENGTHS{{ - 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f -}}; -constexpr auto LATE_ALLPASS_LENGTHS_size = LATE_ALLPASS_LENGTHS.size(); - -/* The late lines are used to approximate the decaying cycle of recursive - * late reflections. - * - * Splitting the lines in half, we start with the shortest reflection paths - * (L_(i=0)^(N/2)): - * - * L_i = 2^(i / (N - 1)) r_d - * - * Then for the opposite (longest) reflection paths (L_(i=N/2)^N): - * - * L_i = 2 r_a - L_(i-N/2) - * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d - * - * For our 1m average room, we get: - */ -constexpr std::array<ALfloat,NUM_LINES> LATE_LINE_LENGTHS{{ - 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f -}}; -constexpr auto LATE_LINE_LENGTHS_size = LATE_LINE_LENGTHS.size(); - - -struct DelayLineI { - /* The delay lines use interleaved samples, with the lengths being powers - * of 2 to allow the use of bit-masking instead of a modulus for wrapping. - */ - ALsizei Mask{0}; - ALfloat (*Line)[NUM_LINES]{nullptr}; - - - void write(ALsizei offset, const ALsizei c, const ALfloat *RESTRICT in, const ALsizei count) const noexcept - { - ASSUME(count > 0); - for(ALsizei i{0};i < count;) - { - offset &= Mask; - ALsizei td{mini(Mask+1 - offset, count - i)}; - do { - Line[offset++][c] = in[i++]; - } while(--td); - } - } -}; - -struct VecAllpass { - DelayLineI Delay; - ALfloat Coeff{0.0f}; - ALsizei Offset[NUM_LINES][2]{}; - - void processFaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo); - void processUnfaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei 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}; - BiquadFilter HFFilter, LFFilter; - - void calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, const ALfloat mfDecayTime, - const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm); - - /* Applies the two T60 damping filter sections. */ - void process(ALfloat *samples, const ALsizei todo) - { - HFFilter.process(samples, samples, todo); - LFFilter.process(samples, samples, todo); - } -}; - -struct EarlyReflections { - /* A Gerzon vector all-pass filter is used to simulate initial diffusion. - * The spread from this filter also helps smooth out the reverb tail. - */ - VecAllpass VecAp; - - /* An echo line is used to complete the second half of the early - * reflections. - */ - DelayLineI Delay; - ALsizei Offset[NUM_LINES][2]{}; - ALfloat Coeff[NUM_LINES][2]{}; - - /* The gain for each output channel based on 3D panning. */ - ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - - void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime, - const ALfloat frequency); -}; - -struct LateReverb { - /* A recursive delay line is used fill in the reverb tail. */ - DelayLineI Delay; - ALsizei Offset[NUM_LINES][2]{}; - - /* Attenuation to compensate for the modal density and decay rate of the - * late lines. - */ - ALfloat DensityGain[2]{0.0f, 0.0f}; - - /* T60 decay filters are used to simulate absorption. */ - T60Filter T60[NUM_LINES]; - - /* 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]{}; - - 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<ALfloat,16> mSampleBuffer; - - struct { - /* Calculated parameters which indicate if cross-fading is needed after - * an update. - */ - ALfloat Density{AL_EAXREVERB_DEFAULT_DENSITY}; - ALfloat Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION}; - ALfloat DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE}; - ALfloat LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE}; - } mParams; - - /* Master effect filters */ - struct { - BiquadFilter Lp; - BiquadFilter Hp; - } mFilter[NUM_LINES]; - - /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mDelay; - - /* Tap points for early reflection delay. */ - ALsizei mEarlyDelayTap[NUM_LINES][2]{}; - ALfloat mEarlyDelayCoeff[NUM_LINES][2]{}; - - /* Tap points for late reverb feed and delay. */ - ALsizei mLateFeedTap{}; - ALsizei mLateDelayTap[NUM_LINES][2]{}; - - /* Coefficients for the all-pass and line scattering matrices. */ - ALfloat mMixX{0.0f}; - ALfloat mMixY{0.0f}; - - EarlyReflections mEarly; - - LateReverb mLate; - - /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */ - ALsizei mFadeCount{0}; - - /* Maximum number of samples to process at once. */ - ALsizei mMaxUpdate[2]{BUFFERSIZE, BUFFERSIZE}; - - /* The current write offset for all delay lines. */ - ALsizei mOffset{0}; - - /* Temporary storage used when processing. */ - alignas(16) std::array<FloatBufferLine,NUM_LINES> mTempSamples{}; - alignas(16) std::array<FloatBufferLine,NUM_LINES> mEarlyBuffer{}; - alignas(16) std::array<FloatBufferLine,NUM_LINES> mLateBuffer{}; - - using MixOutT = void (ReverbState::*)(const al::span<FloatBufferLine> samplesOut, - const ALsizei todo); - - MixOutT mMixOut{&ReverbState::MixOutPlain}; - std::array<ALfloat,MAX_AMBI_ORDER+1> mOrderScales{}; - std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter; - - - void MixOutPlain(const al::span<FloatBufferLine> samplesOut, const ALsizei todo) - { - ASSUME(todo > 0); - - /* Convert back to B-Format, and mix the results to output. */ - for(ALsizei c{0};c < NUM_LINES;c++) - { - std::fill_n(mTempSamples[0].begin(), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, 0, todo); - MixSamples(mTempSamples[0].data(), samplesOut, mEarly.CurrentGain[c], - mEarly.PanGain[c], todo, 0, todo); - } - - for(ALsizei c{0};c < NUM_LINES;c++) - { - std::fill_n(mTempSamples[0].begin(), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, 0, todo); - MixSamples(mTempSamples[0].data(), samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], - todo, 0, todo); - } - } - - void MixOutAmbiUp(const al::span<FloatBufferLine> samplesOut, const ALsizei todo) - { - ASSUME(todo > 0); - - for(ALsizei c{0};c < NUM_LINES;c++) - { - std::fill_n(mTempSamples[0].begin(), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, 0, todo); - - /* Apply scaling to the B-Format's HF response to "upsample" it to - * higher-order output. - */ - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[0][c].applyHfScale(mTempSamples[0].data(), hfscale, todo); - - MixSamples(mTempSamples[0].data(), samplesOut, mEarly.CurrentGain[c], - mEarly.PanGain[c], todo, 0, todo); - } - - for(ALsizei c{0};c < NUM_LINES;c++) - { - std::fill_n(mTempSamples[0].begin(), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, 0, todo); - - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[1][c].applyHfScale(mTempSamples[0].data(), hfscale, todo); - - MixSamples(mTempSamples[0].data(), samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], - todo, 0, todo); - } - } - - 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); - - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - DEF_NEWDEL(ReverbState) -}; - -/************************************** - * Device Update * - **************************************/ - -inline ALfloat CalcDelayLengthMult(ALfloat density) -{ return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); } - -/* Given the allocated sample buffer, this function updates each delay line - * offset. - */ -inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLineI *Delay) -{ - union { - ALfloat *f; - ALfloat (*f4)[NUM_LINES]; - } u; - u.f = &sampleBuffer[reinterpret_cast<ptrdiff_t>(Delay->Line) * NUM_LINES]; - Delay->Line = u.f4; -} - -/* Calculate the length of a delay line and store its mask and offset. */ -ALuint CalcLineLength(const ALfloat length, const ptrdiff_t offset, const ALfloat frequency, - const ALuint extra, DelayLineI *Delay) -{ - /* All line lengths are powers of 2, calculated from their lengths in - * seconds, rounded up. - */ - auto samples = static_cast<ALuint>(float2int(std::ceil(length*frequency))); - samples = NextPowerOf2(samples + extra); - - /* All lines share a single sample buffer. */ - Delay->Mask = samples - 1; - Delay->Line = reinterpret_cast<ALfloat(*)[NUM_LINES]>(offset); - - /* Return the sample count for accumulation. */ - return samples; -} - -/* Calculates the delay line metrics and allocates the shared sample buffer - * for all lines given the sample rate (frequency). If an allocation failure - * occurs, it returns AL_FALSE. - */ -bool ReverbState::allocLines(const ALfloat frequency) -{ - /* All delay line lengths are calculated to accomodate the full range of - * lengths given their respective paramters. - */ - ALuint totalSamples{0u}; - - /* Multiplier for the maximum density value, i.e. density=1, which is - * actually the least density... - */ - ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; - - /* 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. - */ - 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{LATE_LINE_LENGTHS_size}*multiplier}; - totalSamples += CalcLineLength(length, totalSamples, frequency, BUFFERSIZE, &mDelay); - - /* The early vector all-pass line. */ - length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.VecAp.Delay); - - /* The early reflection line. */ - length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.Delay); - - /* The late vector all-pass line. */ - length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.VecAp.Delay); - - /* The late delay lines are calculated from the largest maximum density - * line length. - */ - length = LATE_LINE_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.Delay); - - totalSamples *= NUM_LINES; - if(totalSamples != mSampleBuffer.size()) - { - mSampleBuffer.resize(totalSamples); - mSampleBuffer.shrink_to_fit(); - } - - /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); - - /* Update all delays to reflect the new sample buffer. */ - RealizeLineOffset(mSampleBuffer.data(), &mDelay); - RealizeLineOffset(mSampleBuffer.data(), &mEarly.VecAp.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mEarly.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mLate.VecAp.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mLate.Delay); - - return true; -} - -ALboolean ReverbState::deviceUpdate(const ALCdevice *device) -{ - const auto frequency = static_cast<ALfloat>(device->Frequency); - - /* Allocate the delay lines. */ - if(!allocLines(frequency)) - return AL_FALSE; - - const ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; - - /* The late feed taps are set a fixed position past the latest delay tap. */ - mLateFeedTap = float2int( - (AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier) * frequency); - - /* Clear filters and gain coefficients since the delay lines were all just - * cleared (if not reallocated). - */ - for(auto &filter : 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); - - 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(); - } - - 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 counters and offset base. */ - mFadeCount = 0; - std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), BUFFERSIZE); - mOffset = 0; - - if(device->mAmbiOrder > 1) - { - mMixOut = &ReverbState::MixOutAmbiUp; - mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder); - } - else - { - mMixOut = &ReverbState::MixOutPlain; - 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; -} - -/************************************** - * Effect Update * - **************************************/ - -/* 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); } - -/* 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); } - -/* 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) -{ - /* 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 - * amplitude for the sample n. - * - * Decaying feedback matches exponential decay of the form Sum(a^n), - * where a is the attenuation coefficient, and n is the sample. The area - * under this decay curve can be calculated as: 1 / (1 - a). - * - * Modifying the above equation to find the area under the squared curve - * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be - * calculated by inverting the square root of this approximation, - * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). - */ - return std::sqrt(1.0f - a*a); -} - -/* Calculate the scattering matrix coefficients given a diffusion factor. */ -inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *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)}; - - /* Calculate the first mixing matrix coefficient. */ - *x = std::cos(t); - /* Calculate the second mixing matrix coefficient. */ - *y = std::sin(t) / n; -} - -/* Calculate the limited HF ratio for use with the late reverb low-pass - * filters. - */ -ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF, - const ALfloat decayTime, const ALfloat SpeedOfSound) -{ - /* Find the attenuation due to air absorption in dB (converting delay - * time to meters using the speed of sound). Then reversing the decay - * equation, solve for HF ratio. The delay length is cancelled out of - * the equation, so it can be calculated once for all lines. - */ - ALfloat limitRatio{1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * SpeedOfSound)}; - - /* Using the limit calculated above, apply the upper bound to the HF ratio. - */ - return minf(limitRatio, hfRatio); -} - - -/* Calculates the 3-band T60 damping coefficients for a particular delay line - * 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) -{ - 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)); -} - -/* Update the early reflection line lengths and gain coefficients. */ -void EarlyReflections::updateLines(const ALfloat density, const ALfloat diffusion, - const ALfloat decayTime, const ALfloat 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); - - for(ALsizei i{0};i < NUM_LINES;i++) - { - /* Calculate the length (in seconds) of each all-pass line. */ - ALfloat length{EARLY_ALLPASS_LENGTHS[i] * multiplier}; - - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2int(length * frequency); - - /* Calculate the length (in seconds) of each delay line. */ - length = EARLY_LINE_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2int(length * frequency); - - /* Calculate the gain (coefficient) for each line. */ - Coeff[i][1] = CalcDecayCoeff(length, decayTime); - } -} - -/* 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) -{ - /* 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}; - - const ALfloat late_allpass_avg{ - std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / - float{LATE_ALLPASS_LENGTHS_size}}; - - /* To compensate for changes in modal density and decay time of the late - * reverb signal, the input is attenuated based on the maximal energy of - * the outgoing signal. This approximation is used to keep the apparent - * energy of the signal equal for all ranges of density and decay time. - * - * 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{LATE_LINE_LENGTHS_size} * multiplier}; - length += late_allpass_avg * multiplier; - /* The density gain calculation uses an average decay time weighted by - * approximate bandwidth. This attempts to compensate for losses of energy - * that reduce decay time due to scattering into highly attenuated bands. - */ - const ALfloat bandWeights[3]{ - lf0norm*norm_weight_factor, - hf0norm*norm_weight_factor - lf0norm*norm_weight_factor, - 1.0f - hf0norm*norm_weight_factor}; - DensityGain[1] = CalcDensityGain( - CalcDecayCoeff(length, - bandWeights[0]*lfDecayTime + bandWeights[1]*mfDecayTime + bandWeights[2]*hfDecayTime - ) - ); - - /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f); - - for(ALsizei i{0};i < NUM_LINES;i++) - { - /* Calculate the length (in seconds) of each all-pass line. */ - length = LATE_ALLPASS_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2int(length * frequency); - - /* Calculate the length (in seconds) of each delay line. */ - length = LATE_LINE_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2int(length*frequency + 0.5f); - - /* Approximate the absorption that the vector all-pass would exhibit - * given the current diffusion so we don't have to process a full T60 - * filter for each of its four lines. - */ - length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion) * multiplier; - - /* Calculate the T60 damping coefficients for each line. */ - T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); - } -} - - -/* 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) -{ - 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 - * be applied to adjust the width of the taps. A single-band decay - * coefficient is applied to simulate initial attenuation and absorption. - * - * Late reverb taps are based on the late line lengths to allow a zero- - * delay path and offsets that would continue the propagation naturally - * into the late lines. - */ - for(ALsizei i{0};i < NUM_LINES;i++) - { - ALfloat length{earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier}; - mEarlyDelayTap[i][1] = float2int(length * frequency); - - length = EARLY_TAP_LENGTHS[i]*multiplier; - mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime); - - length = lateDelay + (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front()) / - float{LATE_LINE_LENGTHS_size} * multiplier; - mLateDelayTap[i][1] = mLateFeedTap + float2int(length * frequency); - } -} - -/* Creates a transform matrix given a reverb vector. The vector pans the reverb - * reflections toward the given direction, using its magnitude (up to 1) as a - * 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) -{ - /* 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 - * that the reverb panning vectors use left-handed coordinates, unlike the - * 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])}; - if(mag > 1.0f) - { - norm[0] = vec[0] / mag * -al::MathDefs<float>::Sqrt3(); - norm[1] = vec[1] / mag * al::MathDefs<float>::Sqrt3(); - norm[2] = vec[2] / mag * al::MathDefs<float>::Sqrt3(); - mag = 1.0f; - } - else - { - /* If the magnitude is less than or equal to 1, just apply the sqrt(3) - * term. There's no need to renormalize the magnitude since it would - * just be reapplied in the matrix. - */ - norm[0] = vec[0] * -al::MathDefs<float>::Sqrt3(); - norm[1] = vec[1] * al::MathDefs<float>::Sqrt3(); - norm[2] = vec[2] * al::MathDefs<float>::Sqrt3(); - } - - 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 - }; -} - -/* 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) -{ - /* 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)}; - - mOutTarget = target.Main->Buffer; - for(ALsizei i{0};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(ALsizei i{0};i < NUM_LINES;i++) - { - 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]); - } -} - -void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *Device{Context->Device}; - const ALlistener &Listener = Context->Listener; - const auto frequency = static_cast<ALfloat>(Device->Frequency); - - /* Calculate the master filters */ - ALfloat hf0norm{minf(props->Reverb.HFReference / frequency, 0.49f)}; - /* Restrict the filter gains from going below -60dB to keep the filter from - * killing most of the signal. - */ - ALfloat gainhf{maxf(props->Reverb.GainHF, 0.001f)}; - mFilter[0].Lp.setParams(BiquadType::HighShelf, gainhf, hf0norm, - 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(ALsizei i{1};i < NUM_LINES;i++) - { - mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp); - mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp); - } - - /* Update the main effect delay and associated taps. */ - updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - props->Reverb.Density, props->Reverb.DecayTime, frequency); - - /* Update the early lines. */ - mEarly.updateLines(props->Reverb.Density, props->Reverb.Diffusion, props->Reverb.DecayTime, - frequency); - - /* Get the mixing matrix coefficients. */ - CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY); - - /* If the HF limit parameter is flagged, calculate an appropriate limit - * based on the air absorption parameter. - */ - ALfloat hfRatio{props->Reverb.DecayHFRatio}; - if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) - hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime, Listener.Params.ReverbSpeedOfSound - ); - - /* 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] = mini(BUFFERSIZE, mini(mEarly.Offset[0][1], mLate.Offset[0][1])); - - /* Determine if delay-line cross-fading is required. Density is essentially - * a master control for the feedback delays, so changes the offsets of many - * delay lines. - */ - if(mParams.Density != props->Reverb.Density || - /* Diffusion and decay times influences the decay rate (gain) of the - * late reverb T60 filter. - */ - mParams.Diffusion != props->Reverb.Diffusion || - mParams.DecayTime != props->Reverb.DecayTime || - mParams.HFDecayTime != hfDecayTime || - mParams.LFDecayTime != lfDecayTime || - /* HF/LF References control the weighting used to calculate the density - * gain. - */ - mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference) - mFadeCount = 0; - mParams.Density = props->Reverb.Density; - mParams.Diffusion = props->Reverb.Diffusion; - mParams.DecayTime = props->Reverb.DecayTime; - mParams.HFDecayTime = hfDecayTime; - mParams.LFDecayTime = lfDecayTime; - mParams.HFReference = props->Reverb.HFReference; - mParams.LFReference = props->Reverb.LFReference; -} - - -/************************************** - * Effect Processing * - **************************************/ - -/* Applies a scattering matrix to the 4-line (vector) input. This is used - * for both the below vector all-pass model and to perform modal feed-back - * delay network (FDN) mixing. - * - * The matrix is derived from a skew-symmetric matrix to form a 4D rotation - * matrix with a single unitary rotational parameter: - * - * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 - * [ -a, d, c, -b ] - * [ -b, -c, d, a ] - * [ -c, b, -a, d ] - * - * The rotation is constructed from the effect's diffusion parameter, - * yielding: - * - * 1 = x^2 + 3 y^2 - * - * Where a, b, and c are the coefficient y with differing signs, and d is the - * coefficient x. The final matrix is thus: - * - * [ x, y, -y, y ] n = sqrt(matrix_order - 1) - * [ -y, x, y, y ] t = diffusion_parameter * atan(n) - * [ y, -y, x, y ] x = cos(t) - * [ -y, -y, -y, x ] y = sin(t) / n - * - * Any square orthogonal matrix with an order that is a power of two will - * work (where ^T is transpose, ^-1 is inverse): - * - * M^T = M^-1 - * - * Using that knowledge, finding an appropriate matrix can be accomplished - * naively by searching all combinations of: - * - * M = D + S - S^T - * - * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) - * whose combination of signs are being iterated. - */ -inline void VectorPartialScatter(ALfloat *RESTRICT out, const ALfloat *RESTRICT in, - const ALfloat xCoeff, const ALfloat yCoeff) -{ - 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] ); -} - -/* Utilizes the above, but reverses the input channels. */ -void VectorScatterRevDelayIn(const DelayLineI delay, ALint offset, const ALfloat xCoeff, - const ALfloat yCoeff, const ALsizei base, const al::span<const FloatBufferLine,NUM_LINES> in, - const ALsizei count) -{ - ASSUME(base >= 0); - ASSUME(count > 0); - - for(ALsizei i{0};i < count;) - { - offset &= delay.Mask; - ALsizei td{mini(delay.Mask+1 - offset, count-i)}; - do { - ALfloat f[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) - f[NUM_LINES-1-j] = in[j][base+i]; - ++i; - - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); - } while(--td); - } -} - -/* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass - * filter to the 4-line input. - * - * It works by vectorizing a regular all-pass filter and replacing the delay - * element with a scattering matrix (like the one above) and a diagonal - * matrix of delay elements. - * - * Two static specializations are used for transitional (cross-faded) delay - * line processing and non-transitional processing. - */ -void VecAllpass::processUnfaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei todo) -{ - const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; - - ASSUME(todo > 0); - - ALsizei vap_offset[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) - vap_offset[j] = offset - Offset[j][0]; - for(ALsizei i{0};i < todo;) - { - for(ALsizei j{0};j < NUM_LINES;j++) - vap_offset[j] &= delay.Mask; - offset &= delay.Mask; - - ALsizei maxoff{offset}; - for(ALsizei j{0};j < NUM_LINES;j++) - maxoff = maxi(maxoff, vap_offset[j]); - ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)}; - - do { - ALfloat f[NUM_LINES]; - for(ALsizei j{0};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; - - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); - } while(--td); - } -} -void VecAllpass::processFaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo) -{ - const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; - - ASSUME(todo > 0); - - fade *= 1.0f/FADE_SAMPLES; - ALsizei vap_offset[NUM_LINES][2]; - for(ALsizei j{0};j < NUM_LINES;j++) - { - vap_offset[j][0] = offset - Offset[j][0]; - vap_offset[j][1] = offset - Offset[j][1]; - } - for(ALsizei i{0};i < todo;) - { - for(ALsizei j{0};j < NUM_LINES;j++) - { - vap_offset[j][0] &= delay.Mask; - vap_offset[j][1] &= delay.Mask; - } - offset &= delay.Mask; - - ALsizei maxoff{offset}; - for(ALsizei j{0};j < NUM_LINES;j++) - maxoff = maxi(maxoff, maxi(vap_offset[j][0], vap_offset[j][1])); - ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)}; - - do { - fade += FadeStep; - ALfloat f[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) - f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) + - delay.Line[vap_offset[j][1]++][j]*fade; - - for(ALsizei j{0};j < NUM_LINES;j++) - { - const ALfloat input{samples[j][i]}; - const ALfloat out{f[j] - feedCoeff*input}; - f[j] = input + feedCoeff*out; - - samples[j][i] = out; - } - ++i; - - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); - } while(--td); - } -} - -/* This generates early reflections. - * - * This is done by obtaining the primary reflections (those arriving from the - * same direction as the source) from the main delay line. These are - * attenuated and all-pass filtered (based on the diffusion parameter). - * - * The early lines are then fed in reverse (according to the approximately - * opposite spatial location of the A-Format lines) to create the secondary - * reflections (those arriving from the opposite direction as the source). - * - * The early response is then completed by combining the primary reflections - * with the delayed and attenuated output from the early lines. - * - * 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 EarlyReflection_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out) -{ - const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples}; - const DelayLineI early_delay{State->mEarly.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; - - ASSUME(todo > 0); - - /* First, load decorrelated samples from the main delay line as the primary - * reflections. - */ - for(ALsizei j{0};j < NUM_LINES;j++) - { - ALsizei early_delay_tap{offset - State->mEarlyDelayTap[j][0]}; - const ALfloat coeff{State->mEarlyDelayCoeff[j][0]}; - for(ALsizei i{0};i < todo;) - { - early_delay_tap &= main_delay.Mask; - ALsizei td{mini(main_delay.Mask+1 - early_delay_tap, todo - i)}; - do { - temps[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. - */ - State->mEarly.VecAp.processUnfaded(temps, offset, mixX, mixY, todo); - - /* Apply a delay and bounce to generate secondary reflections, combine with - * the primary reflections and write out the result for mixing. - */ - for(ALsizei j{0};j < NUM_LINES;j++) - { - ALint feedb_tap{offset - State->mEarly.Offset[j][0]}; - const ALfloat feedb_coeff{State->mEarly.Coeff[j][0]}; - - ASSUME(base >= 0); - for(ALsizei i{0};i < todo;) - { - feedb_tap &= early_delay.Mask; - ALsizei td{mini(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - out[j][base+i] = temps[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff; - ++i; - } while(--td); - } - } - for(ALsizei j{0};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, temps[j].data(), todo); - - /* Also write the result back to the main delay line for the late reverb - * stage to pick up at the appropriate time, appplying a scatter and - * bounce to improve the initial diffusion in the late reverb. - */ - const ALsizei late_feed_tap{offset - State->mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, base, - {out.cbegin(), out.cend()}, todo); -} -void EarlyReflection_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALfloat fade, const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out) -{ - const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples}; - const DelayLineI early_delay{State->mEarly.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; - - ASSUME(todo > 0); - - for(ALsizei j{0};j < NUM_LINES;j++) - { - ALsizei early_delay_tap0{offset - State->mEarlyDelayTap[j][0]}; - ALsizei early_delay_tap1{offset - State->mEarlyDelayTap[j][1]}; - const ALfloat oldCoeff{State->mEarlyDelayCoeff[j][0]}; - const ALfloat oldCoeffStep{-oldCoeff / FADE_SAMPLES}; - const ALfloat newCoeffStep{State->mEarlyDelayCoeff[j][1] / FADE_SAMPLES}; - ALfloat fadeCount{fade}; - - for(ALsizei i{0};i < todo;) - { - early_delay_tap0 &= main_delay.Mask; - early_delay_tap1 &= main_delay.Mask; - ALsizei td{mini(main_delay.Mask+1 - maxi(early_delay_tap0, early_delay_tap1), todo-i)}; - do { - fadeCount += 1.0f; - const ALfloat fade0{oldCoeff + oldCoeffStep*fadeCount}; - const ALfloat fade1{newCoeffStep*fadeCount}; - temps[j][i++] = - main_delay.Line[early_delay_tap0++][j]*fade0 + - main_delay.Line[early_delay_tap1++][j]*fade1; - } while(--td); - } - } - - State->mEarly.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo); - - for(ALsizei j{0};j < NUM_LINES;j++) - { - ALint feedb_tap0{offset - State->mEarly.Offset[j][0]}; - ALint feedb_tap1{offset - State->mEarly.Offset[j][1]}; - const ALfloat feedb_oldCoeff{State->mEarly.Coeff[j][0]}; - const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff / FADE_SAMPLES}; - const ALfloat feedb_newCoeffStep{State->mEarly.Coeff[j][1] / FADE_SAMPLES}; - ALfloat fadeCount{fade}; - - ASSUME(base >= 0); - for(ALsizei i{0};i < todo;) - { - feedb_tap0 &= early_delay.Mask; - feedb_tap1 &= early_delay.Mask; - ALsizei td{mini(early_delay.Mask+1 - maxi(feedb_tap0, feedb_tap1), todo - i)}; - - do { - fadeCount += 1.0f; - const ALfloat fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount}; - const ALfloat fade1{feedb_newCoeffStep*fadeCount}; - out[j][base+i] = temps[j][i] + - early_delay.Line[feedb_tap0++][j]*fade0 + - early_delay.Line[feedb_tap1++][j]*fade1; - ++i; - } while(--td); - } - } - for(ALsizei j{0};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, temps[j].data(), todo); - - const ALsizei late_feed_tap{offset - State->mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, base, - {out.cbegin(), out.cend()}, todo); -} - -/* 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. - * - * 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 LateReverb_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out) -{ - const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples}; - const DelayLineI late_delay{State->mLate.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; - - ASSUME(todo > 0); - - /* First, load decorrelated samples from the main and feedback delay lines. - * Filter the signal to apply its frequency-dependent decay. - */ - for(ALsizei j{0};j < NUM_LINES;j++) - { - ALsizei late_delay_tap{offset - State->mLateDelayTap[j][0]}; - ALsizei late_feedb_tap{offset - State->mLate.Offset[j][0]}; - const ALfloat midGain{State->mLate.T60[j].MidGain[0]}; - const ALfloat densityGain{State->mLate.DensityGain[0] * midGain}; - for(ALsizei i{0};i < todo;) - { - late_delay_tap &= main_delay.Mask; - late_feedb_tap &= late_delay.Mask; - ALsizei td{mini( - mini(main_delay.Mask+1 - late_delay_tap, late_delay.Mask+1 - late_feedb_tap), - todo - i)}; - do { - temps[j][i++] = - main_delay.Line[late_delay_tap++][j]*densityGain + - late_delay.Line[late_feedb_tap++][j]*midGain; - } while(--td); - } - State->mLate.T60[j].process(temps[j].data(), todo); - } - - /* Apply a vector all-pass to improve micro-surface diffusion, and write - * out the results for mixing. - */ - State->mLate.VecAp.processUnfaded(temps, offset, mixX, mixY, todo); - - for(ALsizei j{0};j < NUM_LINES;j++) - std::copy_n(temps[j].begin(), todo, out[j].begin()+base); - - /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, base, - {out.cbegin(), out.cend()}, todo); -} -void LateReverb_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALfloat fade, const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out) -{ - const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples}; - const DelayLineI late_delay{State->mLate.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; - - ASSUME(todo > 0); - - for(ALsizei j{0};j < NUM_LINES;j++) - { - const ALfloat oldMidGain{State->mLate.T60[j].MidGain[0]}; - const ALfloat midGain{State->mLate.T60[j].MidGain[1]}; - const ALfloat oldMidStep{-oldMidGain / FADE_SAMPLES}; - const ALfloat midStep{midGain / FADE_SAMPLES}; - const ALfloat oldDensityGain{State->mLate.DensityGain[0] * oldMidGain}; - const ALfloat densityGain{State->mLate.DensityGain[1] * midGain}; - const ALfloat oldDensityStep{-oldDensityGain / FADE_SAMPLES}; - const ALfloat densityStep{densityGain / FADE_SAMPLES}; - ALsizei late_delay_tap0{offset - State->mLateDelayTap[j][0]}; - ALsizei late_delay_tap1{offset - State->mLateDelayTap[j][1]}; - ALsizei late_feedb_tap0{offset - State->mLate.Offset[j][0]}; - ALsizei late_feedb_tap1{offset - State->mLate.Offset[j][1]}; - ALfloat fadeCount{fade}; - - for(ALsizei i{0};i < todo;) - { - late_delay_tap0 &= main_delay.Mask; - late_delay_tap1 &= main_delay.Mask; - late_feedb_tap0 &= late_delay.Mask; - late_feedb_tap1 &= late_delay.Mask; - ALsizei td{mini( - mini(main_delay.Mask+1 - maxi(late_delay_tap0, late_delay_tap1), - late_delay.Mask+1 - maxi(late_feedb_tap0, late_feedb_tap1)), - todo - i)}; - do { - fadeCount += 1.0f; - const ALfloat fade0{oldDensityGain + oldDensityStep*fadeCount}; - const ALfloat fade1{densityStep*fadeCount}; - const ALfloat gfade0{oldMidGain + oldMidStep*fadeCount}; - const ALfloat gfade1{midStep*fadeCount}; - temps[j][i++] = - 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); - } - State->mLate.T60[j].process(temps[j].data(), todo); - } - - State->mLate.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo); - - for(ALsizei j{0};j < NUM_LINES;j++) - std::copy_n(temps[j].begin(), todo, out[j].begin()+base); - - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, base, - {out.cbegin(), out.cend()}, todo); -} - -void ReverbState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) -{ - ALsizei fadeCount{mFadeCount}; - - ASSUME(samplesToDo > 0); - - /* Convert B-Format to A-Format for processing. */ - const al::span<FloatBufferLine,NUM_LINES> afmt{mTempSamples}; - for(ALsizei c{0};c < NUM_LINES;c++) - { - std::fill_n(afmt[c].begin(), samplesToDo, 0.0f); - MixRowSamples(afmt[c], B2A[c], {samplesIn, samplesIn+numInput}, 0, samplesToDo); - - /* Band-pass the incoming samples. */ - mFilter[c].Lp.process(afmt[c].data(), afmt[c].data(), samplesToDo); - mFilter[c].Hp.process(afmt[c].data(), afmt[c].data(), samplesToDo); - } - - /* Process reverb for these samples. */ - for(ALsizei base{0};base < samplesToDo;) - { - ALsizei todo{samplesToDo - base}; - /* If cross-fading, don't do more samples than there are to fade. */ - if(FADE_SAMPLES-fadeCount > 0) - { - todo = mini(todo, FADE_SAMPLES-fadeCount); - todo = mini(todo, mMaxUpdate[0]); - } - todo = mini(todo, mMaxUpdate[1]); - ASSUME(todo > 0 && todo <= BUFFERSIZE); - - const ALsizei offset{mOffset + base}; - ASSUME(offset >= 0); - - /* Feed the initial delay line. */ - for(ALsizei c{0};c < NUM_LINES;c++) - mDelay.write(offset, c, afmt[c].data()+base, todo); - - /* Process the samples for reverb. */ - if(UNLIKELY(fadeCount < FADE_SAMPLES)) - { - auto fade = static_cast<ALfloat>(fadeCount); - - /* Generate early reflections and late reverb. */ - EarlyReflection_Faded(this, offset, todo, fade, base, mEarlyBuffer); - - LateReverb_Faded(this, offset, todo, fade, base, mLateBuffer); - - /* Step fading forward. */ - fadeCount += todo; - if(fadeCount >= FADE_SAMPLES) - { - /* Update the cross-fading delay line taps. */ - fadeCount = FADE_SAMPLES; - for(ALsizei c{0};c < NUM_LINES;c++) - { - mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1]; - mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1]; - mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1]; - mEarly.Offset[c][0] = mEarly.Offset[c][1]; - mEarly.Coeff[c][0] = mEarly.Coeff[c][1]; - mLateDelayTap[c][0] = mLateDelayTap[c][1]; - mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1]; - mLate.Offset[c][0] = mLate.Offset[c][1]; - mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1]; - } - mLate.DensityGain[0] = mLate.DensityGain[1]; - mMaxUpdate[0] = mMaxUpdate[1]; - } - } - else - { - /* Generate early reflections and late reverb. */ - EarlyReflection_Unfaded(this, offset, todo, base, mEarlyBuffer); - - LateReverb_Unfaded(this, offset, todo, base, mLateBuffer); - } - - base += todo; - } - mOffset = (mOffset+samplesToDo) & 0x3fffffff; - mFadeCount = fadeCount; - - /* Finally, mix early reflections and late reverb. */ - (this->*mMixOut)(samplesOut, samplesToDo); -} - - -void EAXReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param); - } -} -void EAXReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ EAXReverb_setParami(props, context, param, vals[0]); } -void EAXReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EAXREVERB_DENSITY: - if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_EAXREVERB_DIFFUSION: - if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_EAXREVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_EAXREVERB_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_EAXREVERB_GAINLF: - if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range"); - props->Reverb.GainLF = val; - break; - - case AL_EAXREVERB_DECAY_TIME: - if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range"); - props->Reverb.DecayLFRatio = val; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_EAXREVERB_ECHO_TIME: - if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range"); - props->Reverb.EchoTime = val; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range"); - props->Reverb.EchoDepth = val; - break; - - case AL_EAXREVERB_MODULATION_TIME: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range"); - props->Reverb.ModulationTime = val; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range"); - props->Reverb.ModulationDepth = val; - break; - - case AL_EAXREVERB_HFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range"); - props->Reverb.HFReference = val; - break; - - case AL_EAXREVERB_LFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range"); - props->Reverb.LFReference = val; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", - param); - } -} -void EAXReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) - { - 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; - } -} - -void EAXReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - alSetError(context, 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) - { - case AL_EAXREVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_EAXREVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_EAXREVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_EAXREVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_EAXREVERB_GAINLF: - *val = props->Reverb.GainLF; - break; - - case AL_EAXREVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - *val = props->Reverb.DecayLFRatio; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_EAXREVERB_ECHO_TIME: - *val = props->Reverb.EchoTime; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - *val = props->Reverb.EchoDepth; - break; - - case AL_EAXREVERB_MODULATION_TIME: - *val = props->Reverb.ModulationTime; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - *val = props->Reverb.ModulationDepth; - break; - - case AL_EAXREVERB_HFREFERENCE: - *val = props->Reverb.HFReference; - break; - - case AL_EAXREVERB_LFREFERENCE: - *val = props->Reverb.LFReference; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", - param); - } -} -void EAXReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ - switch(param) - { - case AL_EAXREVERB_REFLECTIONS_PAN: - vals[0] = props->Reverb.ReflectionsPan[0]; - vals[1] = props->Reverb.ReflectionsPan[1]; - vals[2] = props->Reverb.ReflectionsPan[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - vals[0] = props->Reverb.LateReverbPan[0]; - vals[1] = props->Reverb.LateReverbPan[1]; - vals[2] = props->Reverb.LateReverbPan[2]; - break; - - default: - EAXReverb_getParamf(props, context, param, vals); - break; - } -} - -DEFINE_ALEFFECT_VTABLE(EAXReverb); - - -struct ReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &EAXReverb_vtable; } -}; - -EffectProps ReverbStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ StdReverb_setParami(props, context, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_REVERB_DIFFUSION: - if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_REVERB_GAINHF: - if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_REVERB_DECAY_TIME: - if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_REVERB_DECAY_HFRATIO: - if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ StdReverb_setParamf(props, context, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ StdReverb_getParami(props, context, param, vals); } -void StdReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_REVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_REVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_REVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_REVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_REVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ StdReverb_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(StdReverb); - - -struct StdReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &StdReverb_vtable; } -}; - -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() -{ - static ReverbStateFactory ReverbFactory{}; - return &ReverbFactory; -} - -EffectStateFactory *StdReverbStateFactory_getFactory() -{ - static StdReverbStateFactory ReverbFactory{}; - return &ReverbFactory; -} diff --git a/Alc/effects/vmorpher.cpp b/Alc/effects/vmorpher.cpp deleted file mode 100644 index eebba3f1..00000000 --- a/Alc/effects/vmorpher.cpp +++ /dev/null @@ -1,430 +0,0 @@ -/** - * OpenAL 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. - * - * 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 <cmath> -#include <cstdlib> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" - -namespace { - -#define MAX_UPDATE_SAMPLES 128 -#define NUM_FORMANTS 4 -#define NUM_FILTERS 2 -#define Q_FACTOR 5.0f - -#define VOWEL_A_INDEX 0 -#define VOWEL_B_INDEX 1 - -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS) -#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1) - -inline ALfloat Sin(ALsizei index) -{ - constexpr ALfloat scale{al::MathDefs<float>::Tau() / ALfloat{WAVEFORM_FRACONE}}; - return std::sin(static_cast<ALfloat>(index) * scale)*0.5f + 0.5f; -} - -inline ALfloat Saw(ALsizei index) -{ - return static_cast<ALfloat>(index) / ALfloat{WAVEFORM_FRACONE}; -} - -inline ALfloat Triangle(ALsizei index) -{ - return std::fabs(static_cast<ALfloat>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); -} - -inline ALfloat Half(ALsizei) -{ - return 0.5f; -} - -template<ALfloat func(ALsizei)> -void Oscillate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo) -{ - for(ALsizei i{0};i < todo;i++) - { - index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } -} - -struct FormantFilter -{ - ALfloat f0norm{0.0f}; - ALfloat fGain{1.0f}; - ALfloat s1{0.0f}; - ALfloat s2{0.0f}; - - FormantFilter() = default; - FormantFilter(ALfloat f0norm_, ALfloat gain) : f0norm{f0norm_}, fGain{gain} { } - - inline void process(const ALfloat* samplesIn, ALfloat* samplesOut, const ALsizei 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 = std::tan(al::MathDefs<float>::Pi() * f0norm); - const ALfloat h = 1.0f / (1 + (g / Q_FACTOR) + (g * g)); - - for (ALsizei i{0};i < numInput;i++) - { - const ALfloat H = h * (samplesIn[i] - (1.0f / Q_FACTOR + g) * s1 - s2); - const ALfloat B = g * H + s1; - const ALfloat L = g * B + s2; - - s1 = g * H + B; - s2 = g * B + L; - - // Apply peak and accumulate samples. - samplesOut[i] += B * fGain; - } - } - - inline void clear() - { - s1 = 0.0f; - s2 = 0.0f; - } -}; - - -struct VmorpherState final : public EffectState { - struct { - /* Effect parameters */ - FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; - - /* Effect gains for each channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; - - void (*mGetSamples)(ALfloat* RESTRICT, ALsizei, const ALsizei, ALsizei) {}; - - ALsizei mIndex{0}; - ALsizei mStep{1}; - - /* Effects buffers */ - ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{}; - ALfloat mSampleBufferB[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 ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override; - - static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch); - - DEF_NEWDEL(VmorpherState) -}; - -std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch) -{ - /* Using soprano formant set of values to - * better match mid-range frequency space. - * - * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html - */ - switch(phoneme) - { - case AL_VOCAL_MORPHER_PHONEME_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: - 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: - 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: - 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: - 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); */ - }}; - } - return {}; -} - - -ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/) -{ - for(auto &e : mChans) - { - std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]), - std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), - std::mem_fn(&FormantFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); - } - - return AL_TRUE; -} - -void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) -{ - const ALCdevice *device{context->Device}; - const ALfloat frequency{static_cast<ALfloat>(device->Frequency)}; - const ALfloat step{props->Vmorpher.Rate / static_cast<ALfloat>(device->Frequency)}; - mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1})); - - if(mStep == 0) - mGetSamples = Oscillate<Half>; - else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_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)*/ - mGetSamples = Oscillate<Triangle>; - - const ALfloat pitchA{fastf2i(std::pow(2.0f, props->Vmorpher.PhonemeACoarseTuning*100.0f / 2400.0f)*FRACTIONONE) * (1.0f/FRACTIONONE)}; - const ALfloat pitchB{fastf2i(std::pow(2.0f, props->Vmorpher.PhonemeBCoarseTuning*100.0f / 2400.0f)*FRACTIONONE) * (1.0f/FRACTIONONE)}; - - auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA); - auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB); - - /* Copy the filter coefficients to the input channels. */ - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX])); - } - - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < slot->Wet.Buffer.size();++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } -} - -void VmorpherState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) -{ - /* Following the EFX specification for a conformant implementation which describes - * the effect as a pair of 4-band formant filters blended together using an LFO. - */ - for(ALsizei base{0};base < samplesToDo;) - { - alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES]; - const ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base); - - mGetSamples(lfo, mIndex, mStep, td); - mIndex += (mStep * td) & WAVEFORM_FRACMASK; - mIndex &= WAVEFORM_FRACMASK; - - ASSUME(numInput > 0); - for(ALsizei c{0};c < numInput;c++) - { - for (ALsizei i{0};i < td;i++) - { - mSampleBufferA[i] = 0.0f; - mSampleBufferB[i] = 0.0f; - } - - auto& vowelA = mChans[c].Formants[VOWEL_A_INDEX]; - auto& vowelB = mChans[c].Formants[VOWEL_B_INDEX]; - - /* Process first vowel. */ - vowelA[0].process(&samplesIn[c][base], mSampleBufferA, td); - vowelA[1].process(&samplesIn[c][base], mSampleBufferA, td); - vowelA[2].process(&samplesIn[c][base], mSampleBufferA, td); - vowelA[3].process(&samplesIn[c][base], mSampleBufferA, td); - - /* Process second vowel. */ - vowelB[0].process(&samplesIn[c][base], mSampleBufferB, td); - vowelB[1].process(&samplesIn[c][base], mSampleBufferB, td); - vowelB[2].process(&samplesIn[c][base], mSampleBufferB, td); - vowelB[3].process(&samplesIn[c][base], mSampleBufferB, td); - - alignas(16) ALfloat samplesBlended[MAX_UPDATE_SAMPLES]; - - for (ALsizei i{0};i < td;i++) - samplesBlended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]); - - /* Now, mix the processed sound data to the output. */ - MixSamples(samplesBlended, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains, - samplesToDo-base, base, td); - } - - base += td; - } -} - - -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: - alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param); - } -} -void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, 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: - alSetError(context, 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: - alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param); - } -} -void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, 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: - alSetError(context, 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; } -}; - -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() -{ - static VmorpherStateFactory VmorpherFactory{}; - return &VmorpherFactory; -} diff --git a/Alc/filters/biquad.cpp b/Alc/filters/biquad.cpp deleted file mode 100644 index 6a3cef64..00000000 --- a/Alc/filters/biquad.cpp +++ /dev/null @@ -1,127 +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: - gain = std::sqrt(gain); - 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; - } - - a1 = a[1] / a[0]; - a2 = a[2] / a[0]; - b0 = b[0] / a[0]; - b1 = b[1] / a[0]; - b2 = b[2] / a[0]; -} - -template<typename Real> -void BiquadFilterR<Real>::process(Real *dst, const Real *src, int numsamples) -{ - ASSUME(numsamples > 0); - - const Real b0{this->b0}; - const Real b1{this->b1}; - const Real b2{this->b2}; - const Real a1{this->a1}; - const Real a2{this->a2}; - Real z1{this->z1}; - Real z2{this->z2}; - - /* 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); - - this->z1 = z1; - this->z2 = 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 893a69a9..00000000 --- a/Alc/filters/biquad.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef FILTERS_BIQUAD_H -#define FILTERS_BIQUAD_H - -#include <cmath> -#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 filters, the specified gain is for the - * reference frequency, which is the centerpoint of the transition band. This - * better matches EFX filter design. To set the gain for the shelf itself, use - * the square root of the desired linear gain (or halve the dB gain). - */ - -enum class BiquadType { - /** EFX-style low-pass filter, specifying a gain and reference frequency. */ - HighShelf, - /** EFX-style high-pass filter, specifying a gain and reference frequency. */ - LowShelf, - /** Peaking filter, specifying a gain and reference frequency. */ - Peaking, - - /** Low-pass cut-off filter, specifying a cut-off frequency. */ - LowPass, - /** High-pass cut-off filter, specifying a cut-off frequency. */ - HighPass, - /** Band-pass filter, specifying a center frequency. */ - BandPass, -}; - -template<typename Real> -class BiquadFilterR { - /* Last two delayed components for direct form II. */ - Real z1{0.0f}, z2{0.0f}; - /* Transfer function coefficients "b" (numerator) */ - Real b0{1.0f}, b1{0.0f}, b2{0.0f}; - /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ - Real a1{0.0f}, a2{0.0f}; - -public: - void clear() noexcept { z1 = z2 = 0.0f; } - - /** - * Sets the filter state for the specified filter type and its parameters. - * - * \param type The type of filter to apply. - * \param gain The gain for the reference frequency response. Only used by - * the Shelf and Peaking filter types. - * \param f0norm The reference frequency normal (ref_freq / sample_rate). - * This is the center point for the Shelf, Peaking, and - * BandPass filter types, or the cutoff frequency for the - * LowPass and HighPass filter types. - * \param rcpQ The reciprocal of the Q coefficient for the filter's - * transition band. Can be generated from rcpQFromSlope or - * rcpQFromBandwidth as needed. - */ - void setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ); - - void copyParamsFrom(const BiquadFilterR &other) - { - b0 = other.b0; - b1 = other.b1; - b2 = other.b2; - a1 = other.a1; - a2 = other.a2; - } - - - void process(Real *dst, const Real *src, int numsamples); - - /* Rather hacky. It's just here to support "manual" processing. */ - std::pair<Real,Real> getComponents() const noexcept - { return {z1, z2}; } - void setComponents(Real z1_, Real z2_) noexcept - { z1 = z1_; z2 = z2_; } - Real processOne(const Real in, Real &z1_, Real &z2_) const noexcept - { - Real out{in*b0 + z1_}; - z1_ = in*b1 - out*a1 + z2_; - z2_ = in*b2 - out*a2; - return out; - } - - /** - * 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 1a567f2c..00000000 --- a/Alc/filters/nfc.cpp +++ /dev/null @@ -1,391 +0,0 @@ - -#include "config.h" - -#include "nfc.h" - -#include <algorithm> - -#include "alcmain.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 int 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 int 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 int 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 int 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 b656850a..00000000 --- a/Alc/filters/nfc.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef FILTER_NFC_H -#define FILTER_NFC_H - -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 int count); - - /* Near-field control filter for second-order ambisonic channels (4-8). */ - void process2(float *RESTRICT dst, const float *RESTRICT src, const int count); - - /* Near-field control filter for third-order ambisonic channels (9-15). */ - void process3(float *RESTRICT dst, const float *RESTRICT src, const int count); - - /* Near-field control filter for fourth-order ambisonic channels (16-24). */ - void process4(float *RESTRICT dst, const float *RESTRICT src, const int count); -}; - -#endif /* FILTER_NFC_H */ diff --git a/Alc/filters/splitter.cpp b/Alc/filters/splitter.cpp deleted file mode 100644 index 09e7bfe8..00000000 --- a/Alc/filters/splitter.cpp +++ /dev/null @@ -1,115 +0,0 @@ - -#include "config.h" - -#include "splitter.h" - -#include <cmath> -#include <limits> -#include <algorithm> - -#include "math_defs.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()) - coeff = (std::sin(w) - 1.0f) / cw; - else - coeff = cw * -0.5f; - - lp_z1 = 0.0f; - lp_z2 = 0.0f; - ap_z1 = 0.0f; -} - -template<typename Real> -void BandSplitterR<Real>::process(Real *hpout, Real *lpout, const Real *input, const int count) -{ - ASSUME(count > 0); - - const Real ap_coeff{this->coeff}; - const Real lp_coeff{this->coeff*0.5f + 0.5f}; - Real lp_z1{this->lp_z1}; - Real lp_z2{this->lp_z2}; - Real ap_z1{this->ap_z1}; - auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real - { - /* Low-pass sample processing. */ - Real d{(in - lp_z1) * lp_coeff}; - Real lp_y{lp_z1 + d}; - lp_z1 = lp_y + d; - - d = (lp_y - lp_z2) * lp_coeff; - lp_y = lp_z2 + d; - lp_z2 = lp_y + d; - - *(lpout++) = lp_y; - - /* All-pass sample processing. */ - Real ap_y{in*ap_coeff + ap_z1}; - ap_z1 = in - ap_y*ap_coeff; - - /* High-pass generated from removing low-passed output. */ - return ap_y - lp_y; - }; - std::transform(input, input+count, hpout, proc_sample); - this->lp_z1 = lp_z1; - this->lp_z2 = lp_z2; - this->ap_z1 = ap_z1; -} - -template<typename Real> -void BandSplitterR<Real>::applyHfScale(Real *samples, const Real hfscale, const int count) -{ - ASSUME(count > 0); - - const Real ap_coeff{this->coeff}; - const Real lp_coeff{this->coeff*0.5f + 0.5f}; - Real lp_z1{this->lp_z1}; - Real lp_z2{this->lp_z2}; - Real ap_z1{this->ap_z1}; - auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real - { - /* Low-pass sample processing. */ - Real d{(in - lp_z1) * lp_coeff}; - Real lp_y{lp_z1 + d}; - lp_z1 = lp_y + d; - - d = (lp_y - lp_z2) * lp_coeff; - lp_y = lp_z2 + d; - lp_z2 = lp_y + d; - - /* All-pass sample processing. */ - Real ap_y{in*ap_coeff + ap_z1}; - ap_z1 = in - ap_y*ap_coeff; - - /* High-pass generated from removing low-passed output. */ - return (ap_y-lp_y)*hfscale + lp_y; - }; - std::transform(samples, samples+count, samples, proc_sample); - this->lp_z1 = lp_z1; - this->lp_z2 = lp_z2; - this->ap_z1 = ap_z1; -} - -template<typename Real> -void BandSplitterR<Real>::applyAllpass(Real *samples, const int count) const -{ - ASSUME(count > 0); - - const Real coeff{this->coeff}; - 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 927c4d17..00000000 --- a/Alc/filters/splitter.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef FILTER_SPLITTER_H -#define FILTER_SPLITTER_H - -#include "alcmain.h" -#include "almalloc.h" - - -/* Band splitter. Splits a signal into two phase-matching frequency bands. */ -template<typename Real> -class BandSplitterR { - Real coeff{0.0f}; - Real lp_z1{0.0f}; - Real lp_z2{0.0f}; - Real ap_z1{0.0f}; - -public: - BandSplitterR() = default; - BandSplitterR(const BandSplitterR&) = default; - BandSplitterR(Real f0norm) { init(f0norm); } - - void init(Real f0norm); - void clear() noexcept { lp_z1 = lp_z2 = ap_z1 = 0.0f; } - void process(Real *hpout, Real *lpout, const Real *input, const int count); - - void applyHfScale(Real *samples, const Real hfscale, const int count); - - /* 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 int count) const; -}; -using BandSplitter = BandSplitterR<float>; - - -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) -}; - -#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 e86af6ce..00000000 --- a/Alc/helpers.cpp +++ /dev/null @@ -1,851 +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_PROC_PIDPATH -#include <libproc.h> -#endif - -#ifdef __FreeBSD__ -#include <sys/types.h> -#include <sys/sysctl.h> -#endif - -#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 */ - -#ifdef HAVE_DLFCN_H -#include <dlfcn.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 - -#ifndef _WIN32 -#include <unistd.h> -#elif defined(_WIN32_IE) -#include <shlobj.h> -#endif - -#include "alcmain.h" -#include "almalloc.h" -#include "compat.h" -#include "cpu_caps.h" -#include "fpu_modes.h" -#include "logging.h" - - -#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = unsigned int; -static inline void get_cpuid(int f, reg_type *regs) -{ __get_cpuid(f, ®s[0], ®s[1], ®s[2], ®s[3]); } -#define CAN_GET_CPUID -#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = int; -static inline void get_cpuid(int f, reg_type *regs) -{ (__cpuid)(regs, f); } -#define CAN_GET_CPUID -#endif - -int CPUCapFlags = 0; - -void FillCPUCaps(int capfilter) -{ - int caps = 0; - -/* FIXME: We really should get this for all available CPUs in case different - * CPUs have different caps (is that possible on one machine?). */ -#ifdef CAN_GET_CPUID - union { - reg_type regs[4]; - char str[sizeof(reg_type[4])]; - } cpuinf[3] = {{ { 0, 0, 0, 0 } }}; - - get_cpuid(0, cpuinf[0].regs); - if(cpuinf[0].regs[0] == 0) - ERR("Failed to get CPUID\n"); - else - { - unsigned int maxfunc = cpuinf[0].regs[0]; - unsigned int maxextfunc; - - get_cpuid(0x80000000, cpuinf[0].regs); - maxextfunc = cpuinf[0].regs[0]; - - TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc); - - TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8); - if(maxextfunc >= 0x80000004) - { - get_cpuid(0x80000002, cpuinf[0].regs); - get_cpuid(0x80000003, cpuinf[1].regs); - get_cpuid(0x80000004, cpuinf[2].regs); - TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str); - } - - if(maxfunc >= 1) - { - get_cpuid(1, cpuinf[0].regs); - if((cpuinf[0].regs[3]&(1<<25))) - caps |= CPU_CAP_SSE; - if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26))) - caps |= CPU_CAP_SSE2; - if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0))) - caps |= CPU_CAP_SSE3; - if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19))) - caps |= CPU_CAP_SSE4_1; - } - } -#else - /* Assume support for whatever's supported if we can't check for it */ -#if defined(HAVE_SSE4_1) -#warning "Assuming SSE 4.1 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) -#warning "Assuming SSE 3 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) -#warning "Assuming SSE 2 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) -#warning "Assuming SSE run-time support!" - caps |= CPU_CAP_SSE; -#endif -#endif -#ifdef HAVE_NEON - 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 - -namespace al { - -auto filebuf::underflow() -> int_type -{ - if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr()) - { - // Read in the next chunk of data, and set the pointers on success - DWORD got{}; - if(ReadFile(mFile, mBuffer.data(), (DWORD)mBuffer.size(), &got, nullptr)) - setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got); - } - if(gptr() == egptr()) - return traits_type::eof(); - return traits_type::to_int_type(*gptr()); -} - -auto filebuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) -> pos_type -{ - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos{}; - switch(whence) - { - case std::ios_base::beg: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - break; - - case std::ios_base::cur: - // If the offset remains in the current buffer range, just - // update the pointer. - if((offset >= 0 && offset < off_type(egptr()-gptr())) || - (offset < 0 && -offset <= off_type(gptr()-eback()))) - { - // Get the current file offset to report the correct read - // offset. - fpos.QuadPart = 0; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - setg(eback(), gptr()+offset, egptr()); - return fpos.QuadPart - off_type(egptr()-gptr()); - } - // Need to offset for the file offset being at egptr() while - // the requested offset is relative to gptr(). - offset -= off_type(egptr()-gptr()); - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - break; - - case std::ios_base::end: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END)) - return traits_type::eof(); - break; - - default: - return traits_type::eof(); - } - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; -} - -auto filebuf::seekpos(pos_type pos, std::ios_base::openmode mode) -> pos_type -{ - // Simplified version of seekoff - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos{}; - fpos.QuadPart = pos; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; -} - -filebuf::~filebuf() -{ - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = INVALID_HANDLE_VALUE; -} - -bool filebuf::open(const wchar_t *filename, std::ios_base::openmode mode) -{ - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return false; - HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr)}; - if(f == INVALID_HANDLE_VALUE) return false; - - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = f; - - setg(nullptr, nullptr, nullptr); - return true; -} -bool filebuf::open(const char *filename, std::ios_base::openmode mode) -{ - std::wstring wname{utf8_to_wstr(filename)}; - return open(wname.c_str(), mode); -} - - -ifstream::ifstream(const wchar_t *filename, std::ios_base::openmode mode) - : std::istream{nullptr} -{ - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); -} - -ifstream::ifstream(const char *filename, std::ios_base::openmode mode) - : std::istream{nullptr} -{ - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); -} - -/* This is only here to ensure the compiler doesn't define an implicit - * destructor, which it tries to automatically inline and subsequently complain - * it can't inline without excessive code growth. - */ -ifstream::~ifstream() { } - -} // namespace al - -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 *LoadLib(const char *name) -{ - std::wstring wname{utf8_to_wstr(name)}; - return LoadLibraryW(wname.c_str()); -} -void CloseLib(void *handle) -{ FreeLibrary(static_cast<HMODULE>(handle)); } -void *GetSymbol(void *handle, const char *name) -{ - void *ret{reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name))}; - if(!ret) ERR("Failed to load %s\n", name); - 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)}; - fprintf(logfile, "%ls", wstr.c_str()); - 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) - { - size_t base = results->size(); - do { - results->emplace_back(); - std::string &str = results->back(); - str = path; - str += '\\'; - str += wstr_to_utf8(fdata.cFileName); - TRACE("Got result %s\n", str.c_str()); - } while(FindNextFileW(hdl, &fdata)); - FindClose(hdl); - - std::sort(results->begin()+base, results->end()); - } -} - -al::vector<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. */ - WCHAR *cwdbuf{_wgetenv(L"ALSOFT_LOCAL_PATH")}; - if(cwdbuf && *cwdbuf != '\0') - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - } - else if(!(cwdbuf=_wgetcwd(nullptr, 0))) - path = "."; - else - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - free(cwdbuf); - } - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); - - /* Search the local and global data dirs. */ - static constexpr int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; - for(int id : ids) - { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) - continue; - - path = wstr_to_utf8(buffer); - if(!is_slash(path.back())) - path += '\\'; - path += subdir; - std::replace(path.begin(), path.end(), '/', '\\'); - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority(void) -{ - bool failed = false; - if(RTPrioLevel > 0) - failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - if(failed) ERR("Failed to set priority level for thread\n"); -} - -#else - -const PathNamePair &GetProcBinary() -{ - static PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; - - al::vector<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(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; -} - - -#ifdef HAVE_DLFCN_H - -void *LoadLib(const char *name) -{ - dlerror(); - void *handle{dlopen(name, RTLD_NOW)}; - const char *err{dlerror()}; - if(err) handle = nullptr; - return handle; -} -void CloseLib(void *handle) -{ dlclose(handle); } -void *GetSymbol(void *handle, const char *name) -{ - dlerror(); - void *sym{dlsym(handle, name)}; - const char *err{dlerror()}; - if(err) - { - WARN("Failed to load %s: %s\n", name, err); - sym = nullptr; - } - return sym; -} - -#endif /* HAVE_DLFCN_H */ - -void al_print(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 != nullptr) - { - const size_t extlen = strlen(ext); - size_t base = results->size(); - - struct dirent *dirent; - while((dirent=readdir(dir)) != nullptr) - { - if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) - continue; - - size_t len{strlen(dirent->d_name)}; - if(len <= extlen) continue; - if(strcasecmp(dirent->d_name+len-extlen, ext) != 0) - continue; - - results->emplace_back(); - std::string &str = results->back(); - str = path; - if(str.back() != '/') - str.push_back('/'); - str += dirent->d_name; - TRACE("Got result %s\n", str.c_str()); - } - closedir(dir); - - std::sort(results->begin()+base, results->end()); - } -} - -al::vector<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. */ - const char *str{getenv("ALSOFT_LOCAL_PATH")}; - if(str && *str != '\0') - DirectorySearch(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((str=getenv("XDG_DATA_HOME")) != nullptr && str[0] != '\0') - { - std::string path{str}; - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - else if((str=getenv("HOME")) != nullptr && str[0] != '\0') - { - std::string path{str}; - if(path.back() == '/') - path.pop_back(); - path += "/.local/share/"; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - - // Search global data dirs - if((str=getenv("XDG_DATA_DIRS")) == nullptr || str[0] == '\0') - str = "/usr/local/share/:/usr/share/"; - - const char *next{str}; - while((str=next) != nullptr && str[0] != '\0') - { - next = strchr(str, ':'); - - std::string path = (next ? std::string(str, next++) : std::string(str)); - if(path.empty()) continue; - - if(path.back() != '/') - path += '/'; - path += subdir; - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority() -{ - bool failed = false; -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) - if(RTPrioLevel > 0) - { - struct sched_param param; - /* Use the minimum real-time priority possible for now (on Linux this - * should be 1 for SCHED_RR) */ - param.sched_priority = sched_get_priority_min(SCHED_RR); - failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); - } -#else - /* Real-time priority not available */ - failed = (RTPrioLevel>0); -#endif - if(failed) - ERR("Failed to set priority level for thread\n"); -} - -#endif diff --git a/Alc/hrtf.cpp b/Alc/hrtf.cpp deleted file mode 100644 index 786c4c5d..00000000 --- a/Alc/hrtf.cpp +++ /dev/null @@ -1,1400 +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 <utility> - -#include "AL/al.h" - -#include "alcmain.h" -#include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alspan.h" -#include "compat.h" -#include "filters/splitter.h" -#include "logging.h" -#include "math_defs.h" -#include "opthelpers.h" - - -struct HrtfHandle { - std::unique_ptr<HrtfEntry> entry; - al::FlexArray<char> filename; - - HrtfHandle(size_t fname_len) : filename{fname_len} { } - HrtfHandle(const HrtfHandle&) = delete; - HrtfHandle& operator=(const HrtfHandle&) = delete; - - static std::unique_ptr<HrtfHandle> Create(size_t fname_len); - static constexpr size_t Sizeof(size_t length) noexcept - { - return maxz(sizeof(HrtfHandle), - al::FlexArray<char>::Sizeof(length, offsetof(HrtfHandle, filename))); - } - - DEF_PLACE_NEWDEL() -}; - -std::unique_ptr<HrtfHandle> HrtfHandle::Create(size_t fname_len) -{ - void *ptr{al_calloc(alignof(HrtfHandle), HrtfHandle::Sizeof(fname_len))}; - return std::unique_ptr<HrtfHandle>{new (ptr) HrtfHandle{fname_len}}; -} - -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 (0.05f) -#define MAX_FD_DISTANCE (2.5f) - -#define MIN_EV_COUNT (5) -#define MAX_EV_COUNT (128) - -#define MIN_AZ_COUNT (1) -#define MAX_AZ_COUNT (128) - -#define MAX_HRIR_DELAY (HRTF_HISTORY_LENGTH-1) - -constexpr ALchar magicMarker00[8]{'M','i','n','P','H','R','0','0'}; -constexpr ALchar magicMarker01[8]{'M','i','n','P','H','R','0','1'}; -constexpr ALchar magicMarker02[8]{'M','i','n','P','H','R','0','2'}; - -/* First value for pass-through coefficients (remaining are 0), used for omni- - * directional sounds. */ -constexpr ALfloat PassthruCoeff{0.707106781187f/*sqrt(0.5)*/}; - -std::mutex LoadedHrtfLock; -al::vector<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) * (evcount-1) / al::MathDefs<float>::Pi(); - ALsizei idx{float2int(ev)}; - - return IdxBlend{mini(idx, evcount-1), ev-idx}; -} - -/* Calculate the azimuth index given the polar azimuth in radians. This will - * return an index between 0 and (azcount - 1). - */ -IdxBlend CalcAzIndex(ALsizei azcount, ALfloat az) -{ - az = (al::MathDefs<float>::Tau()+az) * azcount / al::MathDefs<float>::Tau(); - ALsizei idx{float2int(az)}; - - return IdxBlend{idx%azcount, az-idx}; -} - -} // namespace - - -/* Calculates static HRIR coefficients and delays for the given polar elevation - * and azimuth in radians. The coefficients are normalized. - */ -void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance, - ALfloat spread, HrirArray<ALfloat> &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 ALsizei irSize{Hrtf->irSize}; - ASSUME(irSize >= MIN_IR_SIZE); - - /* Calculate the sample offsets for the HRIR indices. */ - idx[0] *= irSize; - idx[1] *= irSize; - idx[2] *= irSize; - idx[3] *= irSize; - - /* Calculate the blended HRIR coefficients. */ - ALfloat *coeffout{al::assume_aligned<16>(&coeffs[0][0])}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill(coeffout+2, coeffout + irSize*2, 0.0f); - for(ALsizei c{0};c < 4;c++) - { - const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]])}; - const ALfloat mult{blend[c]}; - auto blend_coeffs = [mult](const ALfloat src, const ALfloat coeff) noexcept -> ALfloat - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + irSize*2, coeffout, coeffout, blend_coeffs); - } -} - - -std::unique_ptr<DirectHrtfState> DirectHrtfState::Create(size_t num_chans) -{ - void *ptr{al_calloc(16, DirectHrtfState::Sizeof(num_chans))}; - return std::unique_ptr<DirectHrtfState>{new (ptr) DirectHrtfState{num_chans}}; -} - -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, const ALuint NumChannels, - const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], - const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain) -{ - static constexpr int OrderFromChan[MAX_AMBI_CHANNELS]{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }; - /* Set this to true for dual-band HRTF processing. May require better - * calculation of the new IR length to deal with the head and tail - * generated by the HF scaling. - */ - static constexpr bool DualBand{true}; - - ASSUME(NumChannels > 0); - ASSUME(AmbiCount > 0); - - auto &field = Hrtf->field[0]; - ALsizei min_delay{HRTF_HISTORY_LENGTH}; - ALsizei max_delay{0}; - auto idx = al::vector<ALsizei>(AmbiCount); - auto calc_idxs = [Hrtf,&field,&max_delay,&min_delay](const AngularPoint &pt) noexcept -> ALsizei - { - /* Calculate elevation index. */ - const auto evidx = clampi( - static_cast<ALsizei>((90.0f+pt.Elev)*(field.evCount-1)/180.0f + 0.5f), - 0, field.evCount-1); - - const ALsizei azcount{Hrtf->elev[evidx].azCount}; - const ALsizei iroffset{Hrtf->elev[evidx].irOffset}; - - /* Calculate azimuth index for this elevation. */ - const auto azidx = static_cast<ALsizei>((360.0f+pt.Azim)*azcount/360.0f + 0.5f) % azcount; - - /* Calculate the index for the impulse response. */ - ALsizei idx{iroffset + azidx}; - - min_delay = mini(min_delay, mini(Hrtf->delays[idx][0], Hrtf->delays[idx][1])); - max_delay = maxi(max_delay, maxi(Hrtf->delays[idx][0], Hrtf->delays[idx][1])); - - return idx; - }; - std::transform(AmbiPoints, AmbiPoints+AmbiCount, idx.begin(), calc_idxs); - - /* For dual-band processing, add a 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<HrirArray<ALdouble>>(NumChannels); - auto tmpfilt = al::vector<std::array<ALdouble,HRIR_LENGTH*4>>(3); - for(size_t c{0u};c < AmbiCount;++c) - { - const ALfloat (*fir)[2]{&Hrtf->coeffs[idx[c] * Hrtf->irSize]}; - const ALsizei ldelay{Hrtf->delays[idx[c]][0] - min_delay + base_delay}; - const ALsizei rdelay{Hrtf->delays[idx[c]][1] - min_delay + base_delay}; - - if(!DualBand) - { - /* For single-band decoding, apply the HF scale to the response. */ - for(ALuint i{0u};i < NumChannels;++i) - { - const ALdouble mult{ALdouble{AmbiOrderHFGain[OrderFromChan[i]]} * - AmbiMatrix[c][i]}; - const ALsizei numirs{mini(Hrtf->irSize, HRIR_LENGTH-maxi(ldelay, rdelay))}; - ALsizei lidx{ldelay}, ridx{rdelay}; - for(ALsizei j{0};j < numirs;++j) - { - tmpres[i][lidx++][0] += fir[j][0] * mult; - tmpres[i][ridx++][1] += fir[j][1] * mult; - } - } - continue; - } - - /* For dual-band processing, the HRIR needs to be split into low and - * high frequency responses. The band-splitter alone creates frequency- - * dependent phase-shifts, which is not ideal. To counteract it, - * combine it with a backwards phase-shift. - */ - - /* Load the (left) HRIR backwards, into a temp buffer with padding. */ - std::fill(tmpfilt[2].begin(), tmpfilt[2].end(), 0.0); - std::transform(fir, fir+Hrtf->irSize, tmpfilt[2].rbegin() + HRIR_LENGTH*3, - [](const ALfloat (&ir)[2]) noexcept -> ALdouble { return ir[0]; }); - - /* Apply the all-pass on the reversed signal and reverse the resulting - * sample array. This produces the forward response with a backwards - * phase-shift (+n degrees becomes -n degrees). - */ - splitter.applyAllpass(tmpfilt[2].data(), static_cast<int>(tmpfilt[2].size())); - std::reverse(tmpfilt[2].begin(), tmpfilt[2].end()); - - /* Now apply the band-splitter. This applies the normal phase-shift, - * which cancels out with the backwards phase-shift to get the original - * phase on the split signal. - */ - splitter.clear(); - splitter.process(tmpfilt[0].data(), tmpfilt[1].data(), tmpfilt[2].data(), - static_cast<int>(tmpfilt[2].size())); - - /* Apply left ear response with delay and HF scale. */ - for(ALuint i{0u};i < NumChannels;++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALsizei j{HRIR_LENGTH*3 - ldelay}; - for(ALsizei lidx{0};lidx < HRIR_LENGTH;++lidx,++j) - tmpres[i][lidx][0] += (tmpfilt[0][j]*hfgain + tmpfilt[1][j]) * mult; - } - - /* Now run the same process on the right HRIR. */ - std::fill(tmpfilt[2].begin(), tmpfilt[2].end(), 0.0); - std::transform(fir, fir+Hrtf->irSize, tmpfilt[2].rbegin() + HRIR_LENGTH*3, - [](const ALfloat (&ir)[2]) noexcept -> ALdouble { return ir[1]; }); - - splitter.applyAllpass(tmpfilt[2].data(), static_cast<int>(tmpfilt[2].size())); - std::reverse(tmpfilt[2].begin(), tmpfilt[2].end()); - - splitter.clear(); - splitter.process(tmpfilt[0].data(), tmpfilt[1].data(), tmpfilt[2].data(), - static_cast<int>(tmpfilt[2].size())); - - for(ALuint i{0u};i < NumChannels;++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALsizei j{HRIR_LENGTH*3 - rdelay}; - for(ALsizei ridx{0};ridx < HRIR_LENGTH;++ridx,++j) - tmpres[i][ridx][1] += (tmpfilt[0][j]*hfgain + tmpfilt[1][j]) * mult; - } - } - tmpfilt.clear(); - idx.clear(); - - for(ALuint i{0u};i < NumChannels;++i) - { - auto copy_arr = [](const std::array<double,2> &in) noexcept -> std::array<float,2> - { return std::array<float,2>{{static_cast<float>(in[0]), static_cast<float>(in[1])}}; }; - std::transform(tmpres[i].begin(), tmpres[i].end(), state->Chan[i].Coeffs.begin(), - copy_arr); - } - tmpres.clear(); - - ALsizei max_length{HRIR_LENGTH}; - /* Increase the IR size by double the base delay with dual-band processing - * to account for the head and tail from the HF response scale. - */ - const ALsizei irsize{mini(Hrtf->irSize + base_delay*2, max_length)}; - max_length = mini(max_delay-min_delay + irsize, max_length); - - /* Round up to the next IR size multiple. */ - max_length += MOD_IR_SIZE-1; - max_length -= max_length%MOD_IR_SIZE; - - TRACE("Skipped delay: %d, max delay: %d, new FIR length: %d\n", - min_delay, max_delay-min_delay, max_length); - state->IrSize = max_length; -} - - -namespace { - -std::unique_ptr<HrtfEntry> CreateHrtfStore(ALuint rate, ALsizei irSize, const ALsizei fdCount, - const ALubyte *evCount, const ALfloat *distance, const ALushort *azCount, - const ALushort *irOffset, ALsizei irCount, const ALfloat (*coeffs)[2], - const ALubyte (*delays)[2], const char *filename) -{ - std::unique_ptr<HrtfEntry> Hrtf; - - ALsizei evTotal{std::accumulate(evCount, evCount+fdCount, 0)}; - size_t total{sizeof(HrtfEntry)}; - total = RoundUp(total, alignof(HrtfEntry::Field)); /* Align for field infos */ - total += sizeof(HrtfEntry::Field)*fdCount; - total = RoundUp(total, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */ - total += sizeof(Hrtf->elev[0])*evTotal; - total = RoundUp(total, 16); /* Align for coefficients using SIMD */ - total += sizeof(Hrtf->coeffs[0])*irSize*irCount; - total += sizeof(Hrtf->delays[0])*irCount; - - Hrtf.reset(new (al_calloc(16, total)) HrtfEntry{}); - if(!Hrtf) - ERR("Out of memory allocating storage for %s.\n", filename); - else - { - InitRef(&Hrtf->ref, 1u); - Hrtf->sampleRate = rate; - Hrtf->irSize = irSize; - Hrtf->fdCount = fdCount; - - /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast<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])*irSize*irCount; - - auto delays_ = reinterpret_cast<ALubyte(*)[2]>(base + offset); - offset += sizeof(delays_[0])*irCount; - - assert(offset == total); - - /* Copy input data to storage. */ - for(ALsizei i{0};i < fdCount;i++) - { - field_[i].distance = distance[i]; - field_[i].evCount = evCount[i]; - } - for(ALsizei i{0};i < evTotal;i++) - { - elev_[i].azCount = azCount[i]; - elev_[i].irOffset = irOffset[i]; - } - for(ALsizei i{0};i < irSize*irCount;i++) - { - coeffs_[i][0] = coeffs[i][0]; - coeffs_[i][1] = coeffs[i][1]; - } - for(ALsizei i{0};i < irCount;i++) - { - delays_[i][0] = delays[i][0]; - delays_[i][1] = delays[i][1]; - } - - /* Finally, assign the storage pointers. */ - Hrtf->field = field_; - Hrtf->elev = elev_; - Hrtf->coeffs = coeffs_; - Hrtf->delays = delays_; - } - - return Hrtf; -} - -ALubyte GetLE_ALubyte(std::istream &data) -{ - return static_cast<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 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; - - al::vector<ALushort> evOffset(evCount); - for(auto &val : evOffset) - val = GetLE_ALushort(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{1};i < evCount;i++) - { - if(evOffset[i] <= evOffset[i-1]) - { - ERR("Invalid evOffset: evOffset[%d]=%d (last=%d)\n", - i, evOffset[i], evOffset[i-1]); - failed = AL_TRUE; - } - } - if(irCount <= evOffset.back()) - { - ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n", - evOffset.size()-1, evOffset.back(), irCount); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector<ALushort> azCount(evCount); - for(ALsizei i{1};i < evCount;i++) - { - azCount[i-1] = evOffset[i] - evOffset[i-1]; - if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n", - i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - azCount.back() = irCount - evOffset.back(); - if(azCount.back() < MIN_AZ_COUNT || azCount.back() > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n", - azCount.size()-1, azCount.back(), MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector<std::array<ALfloat,2>> coeffs(irSize*irCount); - al::vector<std::array<ALubyte,2>> delays(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(ALsizei i{0};i < evCount;i++) - { - const ALushort evoffset{evOffset[i]}; - const ALushort azcount{azCount[i]}; - for(ALsizei j{0};j < azcount;j++) - { - const ALsizei lidx{evoffset + j}; - const ALsizei ridx{evoffset + ((azcount-j) % azcount)}; - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static constexpr ALfloat distance{0.0f}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast<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; - - al::vector<ALushort> azCount(evCount); - std::generate(azCount.begin(), azCount.end(), std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < evCount;++i) - { - if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n", - i, azCount[i], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - al::vector<ALushort> evOffset(evCount); - evOffset[0] = 0; - ALushort irCount{azCount[0]}; - for(ALsizei i{1};i < evCount;i++) - { - evOffset[i] = evOffset[i-1] + azCount[i-1]; - irCount += azCount[i]; - } - - al::vector<std::array<ALfloat,2>> coeffs(irSize*irCount); - al::vector<std::array<ALubyte,2>> delays(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(ALsizei i{0};i < evCount;i++) - { - const ALushort evoffset{evOffset[i]}; - const ALushort azcount{azCount[i]}; - for(ALsizei j{0};j < azcount;j++) - { - const ALsizei lidx{evoffset + j}; - const ALsizei ridx{evoffset + ((azcount-j) % azcount)}; - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static constexpr ALfloat distance{0.0f}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast<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; - - al::vector<ALfloat> distance(fdCount); - al::vector<ALubyte> evCount(fdCount); - al::vector<ALushort> azCount; - for(ALsizei f{0};f < fdCount;f++) - { - distance[f] = GetLE_ALushort(data) / 1000.0f; - evCount[f] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - if(distance[f] < MIN_FD_DISTANCE || distance[f] > MAX_FD_DISTANCE) - { - ERR("Unsupported field distance[%d]=%f (%f to %f meters)\n", f, - distance[f], MIN_FD_DISTANCE, MAX_FD_DISTANCE); - failed = AL_TRUE; - } - if(f > 0 && distance[f] <= distance[f-1]) - { - ERR("Field distance[%d] is not after previous (%f > %f)\n", f, distance[f], - distance[f-1]); - failed = AL_TRUE; - } - if(evCount[f] < MIN_EV_COUNT || evCount[f] > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount[%d]=%d (%d to %d)\n", f, - evCount[f], MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - size_t ebase{azCount.size()}; - azCount.resize(ebase + evCount[f]); - std::generate(azCount.begin()+ebase, azCount.end(), - std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(ALsizei e{0};e < evCount[f];e++) - { - if(azCount[ebase+e] < MIN_AZ_COUNT || azCount[ebase+e] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d][%d]=%d (%d to %d)\n", f, e, - azCount[ebase+e], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - } - - al::vector<ALushort> evOffset(azCount.size()); - evOffset[0] = 0; - std::partial_sum(azCount.cbegin(), azCount.cend()-1, evOffset.begin()+1); - const ALsizei irTotal{evOffset.back() + azCount.back()}; - - al::vector<std::array<ALfloat,2>> coeffs(irSize*irTotal); - al::vector<std::array<ALubyte,2>> delays(irTotal); - if(channelType == CHANTYPE_LEFTONLY) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - val[0] = GetLE_ALint24(data) / 8388608.0f; - } - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - else if(channelType == CHANTYPE_LEFTRIGHT) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - { - val[0] = GetLE_ALshort(data) / 32768.0f; - val[1] = GetLE_ALshort(data) / 32768.0f; - } - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - { - val[0] = GetLE_ALint24(data) / 8388608.0f; - val[1] = GetLE_ALint24(data) / 8388608.0f; - } - } - for(auto &val : delays) - { - val[0] = GetLE_ALubyte(data); - val[1] = GetLE_ALubyte(data); - } - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(ALsizei i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - if(delays[i][1] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][1]: %d (%d)\n", i, delays[i][1], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - if(failed) - return nullptr; - - if(channelType == CHANTYPE_LEFTONLY) - { - /* Mirror the left ear responses to the right ear. */ - ALsizei ebase{0}; - for(ALsizei f{0};f < fdCount;f++) - { - for(ALsizei e{0};e < evCount[f];e++) - { - const ALushort evoffset{evOffset[ebase+e]}; - const ALushort azcount{azCount[ebase+e]}; - for(ALsizei a{0};a < azcount;a++) - { - const ALsizei lidx{evoffset + a}; - const ALsizei ridx{evoffset + ((azcount-a) % azcount)}; - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - ebase += evCount[f]; - } - } - - if(fdCount > 1) - { - auto distance_ = al::vector<ALfloat>(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 size_t ebase, const ALubyte num_evs) -> size_t - { - auto azcnt_src = azCount.begin()+ebase; - azcnt_end = std::copy_backward(azcnt_src, azcnt_src+num_evs, azcnt_end); - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), size_t{0u}, copy_azs); - assert(azCount_.begin() == azcnt_end); - - /* Reestablish the IR offset for each elevation index, given the new - * ordering of elevations. - */ - evOffset_[0] = 0; - std::partial_sum(azCount_.cbegin(), azCount_.cend()-1, evOffset_.begin()+1); - - /* Reverse the order of each field's group of IRs. */ - auto coeffs_end = coeffs_.end(); - auto delays_end = delays_.end(); - auto copy_irs = [irSize,&azCount,&coeffs,&delays,&coeffs_end,&delays_end](const size_t ebase, const ALubyte num_evs) -> size_t - { - const ALsizei abase{std::accumulate(azCount.cbegin(), azCount.cbegin()+ebase, 0)}; - const ALsizei num_azs{std::accumulate(azCount.cbegin()+ebase, - azCount.cbegin() + (ebase+num_evs), 0)}; - - coeffs_end = std::copy_backward(coeffs.cbegin() + abase*irSize, - coeffs.cbegin() + (abase+num_azs)*irSize, coeffs_end); - delays_end = std::copy_backward(delays.cbegin() + abase, - delays.cbegin() + (abase+num_azs), delays_end); - - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), size_t{0u}, copy_irs); - assert(coeffs_.begin() == coeffs_end); - assert(delays_.begin() == delays_end); - - distance = std::move(distance_); - evCount = std::move(evCount_); - azCount = std::move(azCount_); - evOffset = std::move(evOffset_); - coeffs = std::move(coeffs_); - delays = std::move(delays_); - } - - return CreateHrtfStore(rate, irSize, fdCount, evCount.data(), distance.data(), azCount.data(), - evOffset.data(), irTotal, &reinterpret_cast<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)->filename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - if(loaded_entry == LoadedHrtfs.end()) - { - TRACE("Got new file \"%s\"\n", filename.c_str()); - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+1)); - loaded_entry = LoadedHrtfs.end()-1; - std::copy(filename.begin(), filename.end(), (*loaded_entry)->filename.begin()); - (*loaded_entry)->filename.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\"\n", entry.name.c_str()); -} - -/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer - * for input instead of opening the given filename. - */ -void AddBuiltInEntry(al::vector<EnumeratedHrtf> &list, const std::string &filename, ALuint residx) -{ - auto loaded_entry = LoadedHrtfs.begin(); - for(;loaded_entry != LoadedHrtfs.end();++loaded_entry) - { - if(filename != (*loaded_entry)->filename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - if(loaded_entry == LoadedHrtfs.end()) - { - TRACE("Got new file \"%s\"\n", filename.c_str()); - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+32)); - loaded_entry = LoadedHrtfs.end()-1; - snprintf((*loaded_entry)->filename.data(), (*loaded_entry)->filename.size(), "!%u_%s", - residx, filename.c_str()); - } - - /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - - std::string newname{filename}; - int count{1}; - while(checkName(list, newname)) - { - newname = filename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()}); - const EnumeratedHrtf &entry = list.back(); - - TRACE("Adding built-in entry \"%s\"\n", entry.name.c_str()); -} - - -#define IDR_DEFAULT_44100_MHR 1 -#define IDR_DEFAULT_48000_MHR 2 - -using ResData = al::span<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; - } - } - else if(ConfigValueExists(devname, nullptr, "hrtf_tables")) - ERR("The hrtf_tables option is deprecated, please use hrtf-paths instead.\n"); - - if(usedefaults) - { - for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) - AddFileEntry(list, fname); - - if(!GetResource(IDR_DEFAULT_44100_MHR).empty()) - AddBuiltInEntry(list, "Built-In 44100hz", IDR_DEFAULT_44100_MHR); - - if(!GetResource(IDR_DEFAULT_48000_MHR).empty()) - AddBuiltInEntry(list, "Built-In 48000hz", IDR_DEFAULT_48000_MHR); - } - - if(!list.empty()) - { - if(auto defhrtfopt = ConfigValueStr(devname, nullptr, "default-hrtf")) - { - auto iter = std::find_if(list.begin(), list.end(), - [&defhrtfopt](const EnumeratedHrtf &entry) -> bool - { return entry.name == *defhrtfopt; } - ); - if(iter == list.end()) - WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); - else if(iter != list.begin()) - { - EnumeratedHrtf entry{std::move(*iter)}; - list.erase(iter); - list.insert(list.begin(), std::move(entry)); - } - } - } - - return list; -} - -HrtfEntry *GetLoadedHrtf(HrtfHandle *handle) -{ - std::lock_guard<std::mutex> _{LoadedHrtfLock}; - - if(handle->entry) - { - HrtfEntry *hrtf{handle->entry.get()}; - hrtf->IncRef(); - return hrtf; - } - - std::unique_ptr<std::istream> stream; - const char *name{""}; - ALuint residx{}; - char ch{}; - if(sscanf(handle->filename.data(), "!%u%c", &residx, &ch) == 2 && ch == '_') - { - name = strchr(handle->filename.data(), ch)+1; - - TRACE("Loading %s...\n", name); - ResData res{GetResource(residx)}; - if(res.empty()) - { - ERR("Could not get resource %u, %s\n", residx, name); - return nullptr; - } - stream = al::make_unique<idstream>(res.begin(), res.end()); - } - else - { - name = handle->filename.data(); - - TRACE("Loading %s...\n", handle->filename.data()); - auto fstr = al::make_unique<al::ifstream>(handle->filename.data(), std::ios::binary); - if(!fstr->is_open()) - { - ERR("Could not open %s\n", handle->filename.data()); - return nullptr; - } - stream = std::move(fstr); - } - - std::unique_ptr<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 format: %s %uhz\n", - DevFmtChannelsString(DevFmtStereo), hrtf->sampleRate); - handle->entry = std::move(hrtf); - - return handle->entry.get(); -} - - -void HrtfEntry::IncRef() -{ - auto ref = IncrementRef(&this->ref); - TRACEREF("HrtfEntry %p increasing refcount to %u\n", this, ref); -} - -void HrtfEntry::DecRef() -{ - auto ref = DecrementRef(&this->ref); - TRACEREF("HrtfEntry %p decreasing refcount to %u\n", 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->entry.get()}; - if(entry && ReadRef(&entry->ref) == 0) - { - TRACE("Unloading unused HRTF %s\n", handle->filename.data()); - handle->entry = 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 6c41cb82..00000000 --- a/Alc/hrtf.h +++ /dev/null @@ -1,124 +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 "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 ref; - - ALuint sampleRate; - ALsizei 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. - */ - ALsizei 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>; - -template<typename T> -using HrirArray = std::array<std::array<T,2>,HRIR_LENGTH>; - -struct HrtfState { - alignas(16) std::array<ALfloat,HRTF_HISTORY_LENGTH> History; - alignas(16) HrirArray<ALfloat> Values; -}; - -struct HrtfFilter { - alignas(16) HrirArray<ALfloat> Coeffs; - ALsizei Delay[2]; - ALfloat Gain; -}; - -struct DirectHrtfState { - /* HRTF filter state for dry buffer content */ - ALsizei IrSize{0}; - struct ChanData { - alignas(16) HrirArray<ALfloat> Values; - alignas(16) HrirArray<ALfloat> Coeffs; - }; - al::FlexArray<ChanData> Chan; - - DirectHrtfState(size_t numchans) : Chan{numchans} { } - DirectHrtfState(const DirectHrtfState&) = delete; - DirectHrtfState& operator=(const DirectHrtfState&) = delete; - - static std::unique_ptr<DirectHrtfState> Create(size_t num_chans); - static constexpr size_t Sizeof(size_t numchans) noexcept - { return al::FlexArray<ChanData>::Sizeof(numchans, offsetof(DirectHrtfState, Chan)); } - - DEF_PLACE_NEWDEL() -}; - -struct AngularPoint { - ALfloat Elev; - ALfloat 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<ALfloat> &coeffs, ALsizei (&delays)[2]); - -/** - * Produces HRTF filter coefficients for decoding B-Format, given a set of - * virtual speaker positions, a matching decoding matrix, and per-order high- - * frequency gains for the decoder. The calculated impulse responses are - * ordered and scaled according to the matrix input. Note the specified virtual - * positions should be in degrees, not radians! - */ -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, const ALuint NumChannels, - const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], - const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain); - -#endif /* ALC_HRTF_H */ diff --git a/Alc/inprogext.h b/Alc/inprogext.h deleted file mode 100644 index 15881b59..00000000 --- a/Alc/inprogext.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef INPROGEXT_H -#define INPROGEXT_H - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - -#ifdef __cplusplus -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; -#define AL_MAP_READ_BIT_SOFT 0x00000001 -#define AL_MAP_WRITE_BIT_SOFT 0x00000002 -#define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004 -#define AL_PRESERVE_DATA_BIT_SOFT 0x00000008 -typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer); -typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer); -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length); -#endif -#endif - -#ifndef AL_SOFT_events -#define AL_SOFT_events 1 -#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x1220 -#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x1221 -#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x1222 -#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x1223 -#define AL_EVENT_TYPE_ERROR_SOFT 0x1224 -#define AL_EVENT_TYPE_PERFORMANCE_SOFT 0x1225 -#define AL_EVENT_TYPE_DEPRECATED_SOFT 0x1226 -#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x1227 -typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); -#endif -#endif - -#ifndef AL_SOFT_buffer_layers -#define AL_SOFT_buffer_layers -typedef void (AL_APIENTRY*LPALSOURCEQUEUEBUFFERLAYERSSOFT)(ALuint src, ALsizei nb, const ALuint *buffers); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers); -#endif -#endif - -#ifndef AL_SOFT_effect_chain -#define AL_SOFT_effect_chain -#define AL_EFFECTSLOT_TARGET_SOFT 0xf000 -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* INPROGEXT_H */ diff --git a/Alc/logging.h b/Alc/logging.h deleted file mode 100644 index 0bb0c87b..00000000 --- a/Alc/logging.h +++ /dev/null @@ -1,64 +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(T, ...) fprintf(gLogFile, "AL lib: " T " " __VA_ARGS__) -#else -#define AL_PRINT(T, ...) al_print(gLogFile, "AL lib: " T " " __VA_ARGS__) -#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 TRACEREF(...) do { \ - if(UNLIKELY(gLogLevel >= LogRef)) \ - AL_PRINT("(--)", __VA_ARGS__); \ -} while(0) - -#define TRACE(...) do { \ - if(UNLIKELY(gLogLevel >= LogTrace)) \ - AL_PRINT("(II)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \ -} while(0) - -#define WARN(...) do { \ - if(UNLIKELY(gLogLevel >= LogWarning)) \ - AL_PRINT("(WW)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \ -} while(0) - -#define ERR(...) do { \ - if(UNLIKELY(gLogLevel >= LogError)) \ - AL_PRINT("(EE)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \ -} while(0) - -#endif /* LOGGING_H */ diff --git a/Alc/mastering.cpp b/Alc/mastering.cpp deleted file mode 100644 index 551fdcdf..00000000 --- a/Alc/mastering.cpp +++ /dev/null @@ -1,479 +0,0 @@ -#include "config.h" - -#include <cmath> -#include <limits> -#include <algorithm> -#include <functional> - -#include "mastering.h" -#include "alu.h" -#include "almalloc.h" -#include "math_defs.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]; - ALsizei mExpiries[BUFFERSIZE]; - ALsizei mLowerIndex; - ALsizei mUpperIndex; - ALsizei 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 ALsizei i, const ALfloat in) -{ - static constexpr ALsizei mask{BUFFERSIZE - 1}; - const ALsizei length{Hold->mLength}; - ALfloat (&values)[BUFFERSIZE] = Hold->mValues; - ALsizei (&expiries)[BUFFERSIZE] = Hold->mExpiries; - ALsizei lowerIndex{Hold->mLowerIndex}; - ALsizei upperIndex{Hold->mUpperIndex}; - - ASSUME(upperIndex >= 0); - ASSUME(lowerIndex >= 0); - - 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 ALsizei n) -{ - ASSUME(Hold->mUpperIndex >= 0); - ASSUME(Hold->mLowerIndex >= 0); - - auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex; - auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex; - if(exp_last < exp_begin) - { - std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - std::bind(std::minus<ALsizei>{}, _1, n)); - exp_begin = std::begin(Hold->mExpiries); - } - std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<ALsizei>{}, _1, n)); -} - - -/* Multichannel compression is linked via the absolute maximum of all - * channels. - */ -void LinkChannels(Compressor *Comp, const ALsizei SamplesToDo, const FloatBufferLine *OutBuffer) -{ - const ALsizei index{Comp->mLookAhead}; - const ALuint numChans{Comp->mNumChans}; - - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); - ASSUME(index >= 0); - - auto side_begin = std::begin(Comp->mSideChain) + index; - 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 ALsizei SamplesToDo) -{ - const ALfloat a_crest{Comp->mCrestCoeff}; - const ALsizei index{Comp->mLookAhead}; - ALfloat y2_peak{Comp->mLastPeakSq}; - ALfloat y2_rms{Comp->mLastRmsSq}; - - ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); - - auto calc_crest = [&y2_rms,&y2_peak,a_crest](const ALfloat x_abs) noexcept -> ALfloat - { - ALfloat x2 = maxf(0.000001f, x_abs * x_abs); - - 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) + index; - 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 ALsizei SamplesToDo) -{ - const ALsizei index{Comp->mLookAhead}; - - ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); - - /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ - auto side_begin = std::begin(Comp->mSideChain) + index; - 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 ALsizei SamplesToDo) -{ - const ALsizei index{Comp->mLookAhead}; - - ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); - - SlidingHold *hold{Comp->mHold}; - ALsizei i{0}; - auto detect_peak = [&i,hold](const ALfloat x_abs) -> ALfloat - { - const ALfloat x_G{std::log(maxf(0.000001f, x_abs))}; - return UpdateSlidingHold(hold, i++, x_G); - }; - auto side_begin = std::begin(Comp->mSideChain) + index; - 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 ALsizei SamplesToDo) -{ - const bool autoKnee{Comp->mAuto.Knee}; - const bool autoAttack{Comp->mAuto.Attack}; - const bool autoRelease{Comp->mAuto.Release}; - const bool autoPostGain{Comp->mAuto.PostGain}; - const bool autoDeclip{Comp->mAuto.Declip}; - const ALsizei lookAhead{Comp->mLookAhead}; - const ALfloat threshold{Comp->mThreshold}; - const ALfloat slope{Comp->mSlope}; - const ALfloat attack{Comp->mAttack}; - const ALfloat release{Comp->mRelease}; - const ALfloat c_est{Comp->mGainEstimate}; - const ALfloat a_adp{Comp->mAdaptCoeff}; - const ALfloat (&crestFactor)[BUFFERSIZE] = Comp->mCrestFactor; - ALfloat (&sideChain)[BUFFERSIZE*2] = Comp->mSideChain; - ALfloat postGain{Comp->mPostGain}; - ALfloat knee{Comp->mKnee}; - ALfloat t_att{attack}; - ALfloat t_rel{release - attack}; - ALfloat a_att{std::exp(-1.0f / t_att)}; - ALfloat a_rel{std::exp(-1.0f / t_rel)}; - ALfloat y_1{Comp->mLastRelease}; - ALfloat y_L{Comp->mLastAttack}; - ALfloat c_dev{Comp->mLastGainDev}; - - ASSUME(SamplesToDo > 0); - ASSUME(lookAhead >= 0); - - for(ALsizei i{0};i < SamplesToDo;i++) - { - 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{sideChain[lookAhead+i] - 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[i]}; - 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[i] - y_L - threshold - c_est); - - postGain = -(c_dev + c_est); - } - - sideChain[i] = 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 ALsizei SamplesToDo, FloatBufferLine *OutBuffer) -{ - const ALuint numChans{Comp->mNumChans}; - const ALsizei 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 ALuint SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease, - const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb, - const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime, - const ALfloat ReleaseTime) -{ - const auto lookAhead = static_cast<ALsizei>( - clampf(std::round(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1)); - const auto hold = static_cast<ALsizei>( - 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->mSampleRate = SampleRate; - Comp->mAuto.Knee = AutoKnee != AL_FALSE; - Comp->mAuto.Attack = AutoAttack != AL_FALSE; - Comp->mAuto.Release = AutoRelease != AL_FALSE; - Comp->mAuto.PostGain = AutoPostGain != AL_FALSE; - Comp->mAuto.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]; - } - } - - 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 ALsizei 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); - - ASSUME(mLookAhead >= 0); - 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 34dc8dcb..00000000 --- a/Alc/mastering.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef MASTERING_H -#define MASTERING_H - -#include <memory> - -#include "AL/al.h" - -#include "almalloc.h" -/* For FloatBufferLine/BUFFERSIZE. */ -#include "alcmain.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}; - ALuint mSampleRate{0u}; - - struct { - bool Knee : 1; - bool Attack : 1; - bool Release : 1; - bool PostGain : 1; - bool Declip : 1; - } mAuto{}; - - ALsizei mLookAhead{0}; - - ALfloat mPreGain{0.0f}; - ALfloat mPostGain{0.0f}; - - ALfloat mThreshold{0.0f}; - ALfloat mSlope{0.0f}; - ALfloat mKnee{0.0f}; - - ALfloat mAttack{0.0f}; - ALfloat mRelease{0.0f}; - - alignas(16) ALfloat mSideChain[2*BUFFERSIZE]{}; - alignas(16) ALfloat mCrestFactor[BUFFERSIZE]{}; - - SlidingHold *mHold{nullptr}; - 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 ALsizei SamplesToDo, FloatBufferLine *OutBuffer); - ALsizei getLookAhead() const noexcept { return mLookAhead; } - - DEF_PLACE_NEWDEL() -}; - -/* The compressor is initialized with the following settings: - * - * NumChans - Number of channels to process. - * SampleRate - Sample rate to process. - * AutoKnee - Whether to automate the knee width parameter. - * AutoAttack - Whether to automate the attack time parameter. - * AutoRelease - Whether to automate the release time parameter. - * AutoPostGain - Whether to automate the make-up (post) gain parameter. - * AutoDeclip - Whether to automate clipping reduction. Ignored when - * not automating make-up gain. - * LookAheadTime - Look-ahead time (in seconds). - * HoldTime - Peak hold-time (in seconds). - * PreGainDb - Gain applied before detection (in dB). - * PostGainDb - Make-up gain applied after compression (in dB). - * ThresholdDb - Triggering threshold (in dB). - * Ratio - Compression ratio (x:1). Set to INFINIFTY for true - * limiting. Ignored when automating knee width. - * KneeDb - Knee width (in dB). Ignored when automating knee - * width. - * AttackTimeMin - Attack time (in seconds). Acts as a maximum when - * automating attack time. - * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when - * automating release time. - */ -std::unique_ptr<Compressor> CompressorInit(const ALuint NumChans, const ALuint SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease, - const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb, - const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime, - const ALfloat ReleaseTime); - -#endif /* MASTERING_H */ diff --git a/Alc/mixer/defs.h b/Alc/mixer/defs.h deleted file mode 100644 index 3e5d1125..00000000 --- a/Alc/mixer/defs.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef MIXER_DEFS_H -#define MIXER_DEFS_H - -#include "AL/alc.h" -#include "AL/al.h" - -#include "alcmain.h" -#include "alu.h" -#include "alspan.h" - - -struct MixGains; -struct MixHrtfFilter; -struct HrtfState; -struct DirectHrtfState; - - -struct CTag { }; -struct SSETag { }; -struct SSE2Tag { }; -struct SSE3Tag { }; -struct SSE4Tag { }; -struct NEONTag { }; - -struct CopyTag { }; -struct PointTag { }; -struct LerpTag { }; -struct CubicTag { }; -struct BSincTag { }; - -template<typename TypeTag, typename InstTag> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen); - -template<typename InstTag> -void Mix_(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize); -template<typename InstTag> -void MixRow_(FloatBufferLine &OutBuffer, const ALfloat *Gains, const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, const ALsizei BufferSize); - -template<typename InstTag> -void MixHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, MixHrtfFilter *hrtfparams, const ALsizei BufferSize); -template<typename InstTag> -void MixHrtfBlend_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize); -template<typename InstTag> -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, const ALsizei BufferSize); - -/* Vectorized resampler helpers */ -inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *RESTRICT frac_arr, ALsizei *RESTRICT pos_arr, ALsizei size) -{ - pos_arr[0] = 0; - frac_arr[0] = frac; - for(ALsizei i{1};i < size;i++) - { - ALint frac_tmp = frac_arr[i-1] + increment; - pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS); - frac_arr[i] = frac_tmp&FRACTIONMASK; - } -} - -#endif /* MIXER_DEFS_H */ diff --git a/Alc/mixer/hrtfbase.h b/Alc/mixer/hrtfbase.h deleted file mode 100644 index a76bd62e..00000000 --- a/Alc/mixer/hrtfbase.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef MIXER_HRTFBASE_H -#define MIXER_HRTFBASE_H - -#include <algorithm> - -#include "alu.h" -#include "../hrtf.h" -#include "opthelpers.h" - - -using ApplyCoeffsT = void(ALsizei Offset, float2 *RESTRICT Values, const ALsizei irSize, - const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right); - -template<ApplyCoeffsT &ApplyCoeffs> -inline void MixHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *RESTRICT AccumSamples, const ALsizei OutPos, - const ALsizei IrSize, MixHrtfFilter *hrtfparams, const ALsizei BufferSize) -{ - ASSUME(OutPos >= 0); - ASSUME(IrSize >= 4); - ASSUME(BufferSize > 0); - - const auto &Coeffs = *hrtfparams->Coeffs; - const ALfloat gainstep{hrtfparams->GainStep}; - const ALfloat gain{hrtfparams->Gain}; - - ALsizei Delay[2]{ - HRTF_HISTORY_LENGTH - hrtfparams->Delay[0], - HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] }; - ASSUME(Delay[0] >= 0 && Delay[1] >= 0); - ALfloat stepcount{0.0f}; - for(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat g{gain + gainstep*stepcount}; - const ALfloat left{InSamples[Delay[0]++] * g}; - const ALfloat right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, left, right); - - stepcount += 1.0f; - } - - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[OutPos+i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[OutPos+i] += AccumSamples[i][1]; - - hrtfparams->Gain = gain + gainstep*stepcount; -} - -template<ApplyCoeffsT &ApplyCoeffs> -inline void MixHrtfBlendBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *RESTRICT AccumSamples, const ALsizei OutPos, - const ALsizei IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams, - const ALsizei BufferSize) -{ - const auto &OldCoeffs = oldparams->Coeffs; - const ALfloat oldGain{oldparams->Gain}; - const ALfloat oldGainStep{-oldGain / static_cast<ALfloat>(BufferSize)}; - const auto &NewCoeffs = *newparams->Coeffs; - const ALfloat newGainStep{newparams->GainStep}; - - ASSUME(OutPos >= 0); - ASSUME(IrSize >= 4); - 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); - ALfloat stepcount{0.0f}; - for(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat g{oldGain + oldGainStep*stepcount}; - const ALfloat left{InSamples[Delay[0]++] * g}; - const ALfloat right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(i, 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(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat g{newGainStep*stepcount}; - const ALfloat left{InSamples[Delay[0]++] * g}; - const ALfloat right{InSamples[Delay[1]++] * g}; - ApplyCoeffs(i, AccumSamples+i, IrSize, NewCoeffs, left, right); - - stepcount += 1.0f; - } - - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[OutPos+i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[OutPos+i] += AccumSamples[i][1]; - - newparams->Gain = newGainStep*stepcount; -} - -template<ApplyCoeffsT &ApplyCoeffs> -inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *RESTRICT AccumSamples, - DirectHrtfState *State, const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - const ALsizei IrSize{State->IrSize}; - ASSUME(IrSize >= 4); - - auto chanstate = State->Chan.begin(); - for(const FloatBufferLine &input : InSamples) - { - const auto &Coeffs = chanstate->Coeffs; - - auto accum_iter = std::copy_n(chanstate->Values.begin(), - chanstate->Values.size(), AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); - - for(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat insample{input[i]}; - ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, insample, insample); - } - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[i] += AccumSamples[i][1]; - - std::copy_n(AccumSamples + BufferSize, chanstate->Values.size(), - chanstate->Values.begin()); - ++chanstate; - } -} - -#endif /* MIXER_HRTFBASE_H */ diff --git a/Alc/mixer/mixer_c.cpp b/Alc/mixer/mixer_c.cpp deleted file mode 100644 index 47c4a6f4..00000000 --- a/Alc/mixer/mixer_c.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "config.h" - -#include <cassert> - -#include <limits> - -#include "alcmain.h" -#include "alu.h" -#include "alSource.h" -#include "alAuxEffectSlot.h" -#include "defs.h" -#include "hrtfbase.h" - - -namespace { - -inline ALfloat do_point(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei) -{ return vals[0]; } -inline ALfloat do_lerp(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) -{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); } -inline ALfloat do_cubic(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) -{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); } -inline ALfloat do_bsinc(const InterpState &istate, const ALfloat *RESTRICT vals, const ALsizei frac) -{ - ASSUME(istate.bsinc.m > 0); - - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALsizei pi{frac >> FRAC_PHASE_BITDIFF}; - const ALfloat pf{(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - const ALfloat *fil{istate.bsinc.filter + istate.bsinc.m*pi*4}; - const ALfloat *scd{fil + istate.bsinc.m}; - const ALfloat *phd{scd + istate.bsinc.m}; - const ALfloat *spd{phd + istate.bsinc.m}; - - // Apply the scale and phase interpolated filter. - ALfloat r{0.0f}; - for(ALsizei j_f{0};j_f < istate.bsinc.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; -} - -using SamplerT = ALfloat(const InterpState&, const ALfloat*RESTRICT, const ALsizei); -template<SamplerT &Sampler> -const ALfloat *DoResample(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei numsamples) -{ - ASSUME(numsamples > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - const InterpState istate{*state}; - auto proc_sample = [&src,&frac,istate,increment]() -> ALfloat - { - const ALfloat ret{Sampler(istate, src, frac)}; - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - - return ret; - }; - std::generate_n(dst, numsamples, proc_sample); - - return dst; -} - -} // namespace - -template<> -const ALfloat *Resample_<CopyTag,CTag>(const InterpState*, const ALfloat *RESTRICT src, ALsizei, - ALint, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - ASSUME(dstlen > 0); -#if defined(HAVE_SSE) || defined(HAVE_NEON) - /* Avoid copying the source data if it's aligned like the destination. */ - if((reinterpret_cast<intptr_t>(src)&15) == (reinterpret_cast<intptr_t>(dst)&15)) - return src; -#endif - std::copy_n(src, dstlen, dst); - return dst; -} - -template<> -const ALfloat *Resample_<PointTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample<do_point>(state, src, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_<LerpTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample<do_lerp>(state, src, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_<CubicTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample<do_cubic>(state, src-1, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_<BSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst, dstlen); } - - -static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right) -{ - ASSUME(IrSize >= 2); - for(ALsizei c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } -} - -template<> -void MixHrtf_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfFilter *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - hrtfparams, BufferSize); -} - -template<> -void MixHrtfBlend_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - oldparams, newparams, BufferSize); -} - -template<> -void MixDirectHrtf_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const ALsizei BufferSize) -{ - MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); -} - - -template<> -void Mix_<CTag>(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{output.data()+OutPos}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - ALsizei pos{0}; - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - for(;pos < minsize;pos++) - { - dst[pos] += data[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -/* Basically the inverse of the above. Rather than one input going to multiple - * outputs (each with its own gain), it's multiple inputs (each with its own - * gain) going to one output. This applies one row (vs one column) of a matrix - * transform. And as the matrices are more or less static once set up, no - * stepping is necessary. - */ -template<> -void MixRow_<CTag>(FloatBufferLine &OutBuffer, const ALfloat *Gains, - const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - for(const FloatBufferLine &input : InSamples) - { - const ALfloat *RESTRICT src{input.data()+InPos}; - const ALfloat gain{*(Gains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - for(ALsizei i{0};i < BufferSize;i++) - OutBuffer[i] += src[i] * gain; - } -} diff --git a/Alc/mixer/mixer_neon.cpp b/Alc/mixer/mixer_neon.cpp deleted file mode 100644 index fa487d97..00000000 --- a/Alc/mixer/mixer_neon.cpp +++ /dev/null @@ -1,307 +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" - - - -template<> -const ALfloat *Resample_<LerpTag,NEONTag>(const InterpState*, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const int32x4_t increment4 = vdupq_n_s32(increment*4); - const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE); - const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK); - alignas(16) ALsizei pos_[4], frac_[4]; - int32x4_t pos4, frac4; - ALsizei todo, pos, i; - - ASSUME(frac >= 0); - ASSUME(increment > 0); - ASSUME(dstlen > 0); - - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - frac4 = vld1q_s32(frac_); - pos4 = vld1q_s32(pos_); - - todo = dstlen & ~3; - for(i = 0;i < todo;i += 4) - { - const int pos0 = vgetq_lane_s32(pos4, 0); - const int pos1 = vgetq_lane_s32(pos4, 1); - const int pos2 = vgetq_lane_s32(pos4, 2); - const int pos3 = vgetq_lane_s32(pos4, 3); - const float32x4_t val1 = (float32x4_t){src[pos0], src[pos1], src[pos2], src[pos3]}; - const float32x4_t val2 = (float32x4_t){src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]}; - - /* val1 + (val2-val1)*mu */ - const float32x4_t r0 = vsubq_f32(val2, val1); - const float32x4_t mu = vmulq_f32(vcvtq_f32_s32(frac4), fracOne4); - const float32x4_t out = vmlaq_f32(val1, mu, r0); - - vst1q_f32(&dst[i], out); - - frac4 = vaddq_s32(frac4, increment4); - pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS)); - frac4 = vandq_s32(frac4, fracMask4); - } - - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - pos = vgetq_lane_s32(pos4, 0); - frac = vgetq_lane_s32(frac4, 0); - - for(;i < dstlen;++i) - { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); - - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - -template<> -const ALfloat *Resample_<BSincTag,NEONTag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const ALfloat *const filter = state->bsinc.filter; - const float32x4_t sf4 = vdupq_n_f32(state->bsinc.sf); - const ALsizei m = state->bsinc.m; - const float32x4_t *fil, *scd, *phd, *spd; - ALsizei pi, i, j, offset; - float32x4_t r4; - ALfloat pf; - - ASSUME(m > 0); - ASSUME(dstlen > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - src -= state->bsinc.l; - for(i = 0;i < dstlen;i++) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - pi = frac >> FRAC_PHASE_BITDIFF; - pf = (frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF)); -#undef FRAC_PHASE_BITDIFF - - offset = m*pi*4; - fil = (const float32x4_t*)(filter + offset); offset += m; - scd = (const float32x4_t*)(filter + offset); offset += m; - phd = (const float32x4_t*)(filter + offset); offset += m; - spd = (const float32x4_t*)(filter + offset); - - // Apply the scale and phase interpolated filter. - r4 = vdupq_n_f32(0.0f); - { - const ALsizei count = m >> 2; - const float32x4_t pf4 = vdupq_n_f32(pf); - - ASSUME(count > 0); - - for(j = 0;j < count;j++) - { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const float32x4_t f4 = vmlaq_f32( - vmlaq_f32(fil[j], sf4, scd[j]), - pf4, vmlaq_f32(phd[j], sf4, spd[j]) - ); - /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j*4])); - } - } - r4 = vaddq_f32(r4, vcombine_f32(vrev64_f32(vget_high_f32(r4)), - vrev64_f32(vget_low_f32(r4)))); - dst[i] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - - -static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right) -{ - ASSUME(IrSize >= 2); - - float32x4_t leftright4; - { - float32x2_t leftright2 = vdup_n_f32(0.0); - leftright2 = vset_lane_f32(left, leftright2, 0); - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } - - for(ALsizei c{0};c < IrSize;c += 2) - { - float32x4_t vals = vcombine_f32(vld1_f32((float32_t*)&Values[c ][0]), - vld1_f32((float32_t*)&Values[c+1][0])); - float32x4_t coefs = vld1q_f32((float32_t*)&Coeffs[c][0]); - - vals = vmlaq_f32(vals, coefs, leftright4); - - vst1_f32((float32_t*)&Values[c ][0], vget_low_f32(vals)); - vst1_f32((float32_t*)&Values[c+1][0], vget_high_f32(vals)); - } -} - -template<> -void MixHrtf_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfFilter *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - hrtfparams, BufferSize); -} - -template<> -void MixHrtfBlend_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - oldparams, newparams, BufferSize); -} - -template<> -void MixDirectHrtf_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const ALsizei BufferSize) -{ - MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); -} - - -template<> -void Mix_<NEONTag>(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f}; - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - ALsizei pos{0}; - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(LIKELY(minsize > 3)) - { - const float32x4_t four4{vdupq_n_f32(4.0f)}; - const float32x4_t step4{vdupq_n_f32(step)}; - const float32x4_t gain4{vdupq_n_f32(gain)}; - float32x4_t step_count4{vsetq_lane_f32(0.0f, - vsetq_lane_f32(1.0f, - vsetq_lane_f32(2.0f, - vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3), - 2), 1), 0 - )}; - ALsizei todo{minsize >> 2}; - - do { - const float32x4_t val4 = vld1q_f32(&data[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = vgetq_lane_f32(step_count4, 0); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(;pos < minsize;pos++) - { - dst[pos] += data[pos]*(gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - minsize = mini(BufferSize, (pos+3)&~3); - for(;pos < minsize;pos++) - dst[pos] += data[pos]*gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(LIKELY(BufferSize-pos > 3)) - { - ALsizei todo{(BufferSize-pos) >> 2}; - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(&data[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -template<> -void MixRow_<NEONTag>(FloatBufferLine &OutBuffer, const ALfloat *Gains, - const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - for(const FloatBufferLine &input : InSamples) - { - const ALfloat *RESTRICT src{al::assume_aligned<16>(input.data()+InPos)}; - const ALfloat gain{*(Gains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - ALsizei pos{0}; - if(LIKELY(BufferSize > 3)) - { - ALsizei todo{BufferSize >> 2}; - float32x4_t gain4{vdupq_n_f32(gain)}; - do { - const float32x4_t val4 = vld1q_f32(&src[pos]); - float32x4_t dry4 = vld1q_f32(&OutBuffer[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&OutBuffer[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - OutBuffer[pos] += src[pos]*gain; - } -} diff --git a/Alc/mixer/mixer_sse.cpp b/Alc/mixer/mixer_sse.cpp deleted file mode 100644 index b763fdbd..00000000 --- a/Alc/mixer/mixer_sse.cpp +++ /dev/null @@ -1,262 +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 "alSource.h" -#include "alAuxEffectSlot.h" -#include "defs.h" -#include "hrtfbase.h" - - -template<> -const ALfloat *Resample_<BSincTag,SSETag>(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const ALfloat *const filter{state->bsinc.filter}; - const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; - const ALsizei m{state->bsinc.m}; - - ASSUME(m > 0); - ASSUME(dstlen > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - src -= state->bsinc.l; - for(ALsizei i{0};i < dstlen;i++) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALsizei pi{frac >> FRAC_PHASE_BITDIFF}; - const ALfloat pf{(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF))}; -#undef FRAC_PHASE_BITDIFF - - ALsizei offset{m*pi*4}; - const __m128 *fil{reinterpret_cast<const __m128*>(filter + offset)}; offset += m; - const __m128 *scd{reinterpret_cast<const __m128*>(filter + offset)}; offset += m; - const __m128 *phd{reinterpret_cast<const __m128*>(filter + offset)}; offset += m; - const __m128 *spd{reinterpret_cast<const __m128*>(filter + offset)}; - - // Apply the scale and phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; - { - const ALsizei count{m >> 2}; - const __m128 pf4{_mm_set1_ps(pf)}; - - ASSUME(count > 0); - -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - for(ALsizei j{0};j < count;j++) - { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __m128 f4 = MLA4( - MLA4(fil[j], sf4, scd[j]), - pf4, MLA4(phd[j], sf4, spd[j]) - ); - /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j*4])); - } -#undef MLA4 - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - dst[i] = _mm_cvtss_f32(r4); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - - -static inline void ApplyCoeffs(ALsizei Offset, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right) -{ - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - - ASSUME(IrSize >= 2); - - if((Offset&1)) - { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))}; - imp0 = _mm_mul_ps(lrlr, coeffs); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals); - ALsizei i{1}; - for(;i < IrSize-1;i += 2) - { - coeffs = _mm_load_ps(&Coeffs[i+1][0]); - vals = _mm_load_ps(&Values[i][0]); - imp1 = _mm_mul_ps(lrlr, coeffs); - imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); - vals = _mm_add_ps(imp0, vals); - _mm_store_ps(&Values[i][0], vals); - imp0 = imp1; - } - vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0])); - imp0 = _mm_movehl_ps(imp0, imp0); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); - } - else - { - for(ALsizei i{0};i < IrSize;i += 2) - { - __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; - __m128 vals{_mm_load_ps(&Values[i][0])}; - vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs)); - _mm_store_ps(&Values[i][0], vals); - } - } -} - -template<> -void MixHrtf_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfFilter *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - hrtfparams, BufferSize); -} - -template<> -void MixHrtfBlend_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, OutPos, IrSize, - oldparams, newparams, BufferSize); -} - -template<> -void MixDirectHrtf_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, - const ALsizei BufferSize) -{ - MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); -} - - -template<> -void Mix_<SSETag>(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; - for(FloatBufferLine &output : OutBuffer) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - ALfloat gain{*CurrentGains}; - const ALfloat diff{*TargetGains - gain}; - - ALsizei pos{0}; - if(std::fabs(diff) > std::numeric_limits<float>::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(LIKELY(minsize > 3)) - { - const __m128 four4{_mm_set1_ps(4.0f)}; - const __m128 step4{_mm_set1_ps(step)}; - const __m128 gain4{_mm_set1_ps(gain)}; - __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; - ALsizei todo{minsize >> 2}; - do { - const __m128 val4{_mm_load_ps(&data[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); -#undef MLA4 - _mm_store_ps(&dst[pos], dry4); - step_count4 = _mm_add_ps(step_count4, four4); - pos += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = _mm_cvtss_f32(step_count4); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(;pos < minsize;pos++) - { - dst[pos] += data[pos]*(gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = *TargetGains; - else - gain += step*step_count; - *CurrentGains = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - minsize = mini(BufferSize, (pos+3)&~3); - for(;pos < minsize;pos++) - dst[pos] += data[pos]*gain; - } - ++CurrentGains; - ++TargetGains; - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(LIKELY(BufferSize-pos > 3)) - { - ALsizei todo{(BufferSize-pos) >> 2}; - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(&data[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&dst[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -template<> -void MixRow_<SSETag>(FloatBufferLine &OutBuffer, const ALfloat *Gains, - const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(BufferSize > 0); - - for(const FloatBufferLine &input : InSamples) - { - const ALfloat *RESTRICT src{al::assume_aligned<16>(input.data()+InPos)}; - const ALfloat gain{*(Gains++)}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - ALsizei pos{0}; - if(LIKELY(BufferSize > 3)) - { - ALsizei todo{BufferSize >> 2}; - const __m128 gain4 = _mm_set1_ps(gain); - do { - const __m128 val4{_mm_load_ps(&src[pos])}; - __m128 dry4{_mm_load_ps(&OutBuffer[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&OutBuffer[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - OutBuffer[pos] += src[pos]*gain; - } -} diff --git a/Alc/mixer/mixer_sse2.cpp b/Alc/mixer/mixer_sse2.cpp deleted file mode 100644 index b5d00106..00000000 --- a/Alc/mixer/mixer_sse2.cpp +++ /dev/null @@ -1,84 +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, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const __m128i increment4{_mm_set1_epi32(increment*4)}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - ASSUME(frac > 0); - ASSUME(increment > 0); - ASSUME(dstlen >= 0); - - alignas(16) ALsizei pos_[4], frac_[4]; - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3])}; - __m128i pos4{_mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3])}; - - const ALsizei todo{dstlen & ~3}; - for(ALsizei i{0};i < todo;i += 4) - { - const int pos0{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)))}; - const int pos1{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)))}; - const int pos2{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)))}; - const int pos3{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)))}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; - - /* val1 + (val2-val1)*mu */ - const __m128 r0{_mm_sub_ps(val2, val1)}; - const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; - const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - - _mm_store_ps(&dst[i], out); - - frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); - frac4 = _mm_and_si128(frac4, fracMask4); - } - - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - ALsizei pos{_mm_cvtsi128_si32(pos4)}; - frac = _mm_cvtsi128_si32(frac4); - - for(ALsizei i{todo};i < dstlen;++i) - { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); - - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} diff --git a/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 7efbda7b..00000000 --- a/Alc/mixer/mixer_sse41.cpp +++ /dev/null @@ -1,85 +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, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const __m128i increment4{_mm_set1_epi32(increment*4)}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - ASSUME(frac > 0); - ASSUME(increment > 0); - ASSUME(dstlen >= 0); - - alignas(16) ALsizei pos_[4], frac_[4]; - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3])}; - __m128i pos4{_mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3])}; - - const ALsizei todo{dstlen & ~3}; - for(ALsizei i{0};i < todo;i += 4) - { - const int pos0{_mm_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[i], out); - - frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); - frac4 = _mm_and_si128(frac4, fracMask4); - } - - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - ALsizei pos{_mm_cvtsi128_si32(pos4)}; - frac = _mm_cvtsi128_si32(frac4); - - for(ALsizei i{todo};i < dstlen;++i) - { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); - - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} diff --git a/Alc/mixvoice.cpp b/Alc/mixvoice.cpp deleted file mode 100644 index be872f6d..00000000 --- a/Alc/mixvoice.cpp +++ /dev/null @@ -1,954 +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 <algorithm> -#include <array> -#include <atomic> -#include <cassert> -#include <climits> -#include <cstddef> -#include <cstdint> -#include <cstdlib> -#include <cstring> -#include <iterator> -#include <memory> -#include <new> -#include <numeric> -#include <string> -#include <utility> - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alBuffer.h" -#include "alcmain.h" -#include "alSource.h" -#include "albyte.h" -#include "alconfig.h" -#include "alcontext.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alspan.h" -#include "alu.h" -#include "cpu_caps.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!"); - -/* BSinc24 requires up to 23 extra samples before the current position, and 24 after. */ -static_assert(MAX_RESAMPLE_PADDING >= 24, "MAX_RESAMPLE_PADDING must be at least 24!"); - - -Resampler ResamplerDefault = LinearResampler; - -MixerFunc MixSamples = Mix_<CTag>; -RowMixerFunc MixRowSamples = MixRow_<CTag>; -static HrtfMixerFunc MixHrtfSamples = MixHrtf_<CTag>; -static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_<CTag>; - -static 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>; -} - -static 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>; -} - -static 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>; -} - -static 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>; -} - -ResamplerFunc SelectResampler(Resampler resampler) -{ - switch(resampler) - { - case PointResampler: - return Resample_<PointTag,CTag>; - case LinearResampler: -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_<LerpTag,NEONTag>; -#endif -#ifdef HAVE_SSE4_1 - if((CPUCapFlags&CPU_CAP_SSE4_1)) - return Resample_<LerpTag,SSE4Tag>; -#endif -#ifdef HAVE_SSE2 - if((CPUCapFlags&CPU_CAP_SSE2)) - return Resample_<LerpTag,SSE2Tag>; -#endif - return Resample_<LerpTag,CTag>; - case FIR4Resampler: - return Resample_<CubicTag,CTag>; - case BSinc12Resampler: - case BSinc24Resampler: -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_<BSincTag,NEONTag>; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return Resample_<BSincTag,SSETag>; -#endif - return Resample_<BSincTag,CTag>; - } - - return Resample_<PointTag,CTag>; -} - - -void aluInitMixer() -{ - if(auto resopt = ConfigValueStr(nullptr, nullptr, "resampler")) - { - const char *str{resopt->c_str()}; - if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0) - ResamplerDefault = PointResampler; - else if(strcasecmp(str, "linear") == 0) - ResamplerDefault = LinearResampler; - else if(strcasecmp(str, "cubic") == 0) - ResamplerDefault = FIR4Resampler; - else if(strcasecmp(str, "bsinc12") == 0) - ResamplerDefault = BSinc12Resampler; - else if(strcasecmp(str, "bsinc24") == 0) - ResamplerDefault = BSinc24Resampler; - else if(strcasecmp(str, "bsinc") == 0) - { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); - ResamplerDefault = BSinc12Resampler; - } - else if(strcasecmp(str, "sinc4") == 0 || strcasecmp(str, "sinc8") == 0) - { - WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); - ResamplerDefault = FIR4Resampler; - } - else - { - char *end; - long n = strtol(str, &end, 0); - if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler)) - ResamplerDefault = static_cast<Resampler>(n); - else - WARN("Invalid resampler: %s\n", str); - } - } - - MixHrtfBlendSamples = SelectHrtfBlendMixer(); - MixHrtfSamples = SelectHrtfMixer(); - MixSamples = SelectMixer(); - MixRowSamples = SelectRowMixer(); -} - - -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 -}; - - -void SendSourceStoppedEvent(ALCcontext *context, ALuint id) -{ - ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)}; - if(!(enabledevt&EventType_SourceStateChange)) return; - - RingBuffer *ring{context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; - - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}}; - evt->u.srcstate.id = id; - evt->u.srcstate.state = AL_STOPPED; - - ring->writeAdvance(1); - context->EventSem.post(); -} - - -const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter, ALfloat *dst, - const ALfloat *src, ALsizei 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; -} - - -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ -template<FmtType T> -inline ALfloat LoadSample(typename FmtTypeTraits<T>::Type val); - -template<> inline ALfloat LoadSample<FmtUByte>(FmtTypeTraits<FmtUByte>::Type val) -{ return (val-128) * (1.0f/128.0f); } -template<> inline ALfloat LoadSample<FmtShort>(FmtTypeTraits<FmtShort>::Type val) -{ return val * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample<FmtFloat>(FmtTypeTraits<FmtFloat>::Type val) -{ return val; } -template<> inline ALfloat LoadSample<FmtDouble>(FmtTypeTraits<FmtDouble>::Type val) -{ return static_cast<ALfloat>(val); } -template<> inline ALfloat LoadSample<FmtMulaw>(FmtTypeTraits<FmtMulaw>::Type val) -{ return muLawDecompressionTable[val] * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample<FmtAlaw>(FmtTypeTraits<FmtAlaw>::Type val) -{ return aLawDecompressionTable[val] * (1.0f/32768.0f); } - -template<FmtType T> -inline void LoadSampleArray(ALfloat *RESTRICT dst, const al::byte *src, ALint srcstep, - const ptrdiff_t samples) -{ - using SampleType = typename FmtTypeTraits<T>::Type; - - const SampleType *RESTRICT ssrc{reinterpret_cast<const SampleType*>(src)}; - for(ALsizei i{0};i < samples;i++) - dst[i] += LoadSample<T>(ssrc[i*srcstep]); -} - -void LoadSamples(ALfloat *RESTRICT dst, const al::byte *src, ALint srcstep, FmtType srctype, - const ptrdiff_t samples) -{ -#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 ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, - al::span<ALfloat> SrcBuffer) -{ - /* TODO: For static sources, loop points are taken from the first buffer - * (should be adjusted by any buffer offset, to possibly be added later). - */ - const ALbuffer *Buffer0{BufferListItem->buffers[0]}; - const ALsizei LoopStart{Buffer0->LoopStart}; - const ALsizei LoopEnd{Buffer0->LoopEnd}; - ASSUME(LoopStart >= 0); - ASSUME(LoopEnd > LoopStart); - - /* If current pos is beyond the loop range, do not loop */ - if(!BufferLoopItem || DataPosInt >= LoopEnd) - { - BufferLoopItem = nullptr; - - auto load_buffer = [DataPosInt,NumChannels,SampleSize,chan,SrcBuffer](size_t CompLen, const ALbuffer *buffer) -> size_t - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left to play from the buffer */ - const size_t DataSize{std::min<size_t>(SrcBuffer.size(), - buffer->SampleLen - DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const al::byte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - /* It's impossible to have a buffer list item with no entries. */ - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcBuffer = SrcBuffer.subspan(std::accumulate(BufferListItem->buffers, buffers_end, - size_t{0u}, load_buffer)); - } - else - { - const al::span<ALfloat> SrcData{SrcBuffer.first( - std::min<size_t>(SrcBuffer.size(), LoopEnd - DataPosInt))}; - - auto load_buffer = [DataPosInt,NumChannels,SampleSize,chan,SrcData](size_t CompLen, const ALbuffer *buffer) -> size_t - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left of this loop iteration */ - const size_t DataSize{std::min<size_t>(SrcData.size(), - buffer->SampleLen - DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const al::byte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData.data(), Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcBuffer = SrcBuffer.subspan(std::accumulate(BufferListItem->buffers, buffers_end, - size_t{0u}, load_buffer)); - - const auto LoopSize = static_cast<size_t>(LoopEnd - LoopStart); - while(!SrcBuffer.empty()) - { - const al::span<ALfloat> SrcData{SrcBuffer.first( - std::min<size_t>(SrcBuffer.size(), LoopSize))}; - - auto load_buffer_loop = [LoopStart,NumChannels,SampleSize,chan,SrcData](size_t CompLen, const ALbuffer *buffer) -> size_t - { - if(LoopStart >= buffer->SampleLen) - return CompLen; - - const size_t DataSize{std::min<size_t>(SrcData.size(), - buffer->SampleLen-LoopStart)}; - CompLen = std::max(CompLen, DataSize); - - const al::byte *Data{buffer->mData.data()}; - Data += (LoopStart*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData.data(), Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - SrcBuffer = SrcBuffer.subspan(std::accumulate(BufferListItem->buffers, buffers_end, - size_t{0u}, load_buffer_loop)); - } - } - return SrcBuffer.begin(); -} - -ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, - const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, - al::span<ALfloat> SrcBuffer) -{ - /* Crawl the buffer queue to fill in the temp buffer */ - while(BufferListItem && !SrcBuffer.empty()) - { - if(DataPosInt >= BufferListItem->max_samples) - { - DataPosInt -= BufferListItem->max_samples; - BufferListItem = BufferListItem->next.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - continue; - } - - auto load_buffer = [DataPosInt,NumChannels,SampleSize,chan,SrcBuffer](size_t CompLen, const ALbuffer *buffer) -> size_t - { - if(!buffer) return CompLen; - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - const size_t DataSize{std::min<size_t>(SrcBuffer.size(), buffer->SampleLen-DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const al::byte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcBuffer.data(), Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcBuffer = SrcBuffer.subspan(std::accumulate(BufferListItem->buffers, buffers_end, - size_t{0u}, load_buffer)); - - if(SrcBuffer.empty()) - break; - DataPosInt = 0; - BufferListItem = BufferListItem->next.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - } - - return SrcBuffer.begin(); -} - -} // namespace - -void MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo) -{ - static constexpr ALfloat SilentTarget[MAX_OUTPUT_CHANNELS]{}; - - ASSUME(SamplesToDo > 0); - - /* Get voice info */ - const bool isstatic{(voice->mFlags&VOICE_IS_STATIC) != 0}; - ALsizei DataPosInt{static_cast<ALsizei>(voice->mPosition.load(std::memory_order_relaxed))}; - ALsizei DataPosFrac{voice->mPositionFrac.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferListItem{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferLoopItem{voice->mLoopBuffer.load(std::memory_order_relaxed)}; - const ALsizei NumChannels{voice->mNumChannels}; - const ALsizei SampleSize{voice->mSampleSize}; - const ALint increment{voice->mStep}; - - ASSUME(DataPosInt >= 0); - ASSUME(DataPosFrac >= 0); - ASSUME(NumChannels > 0); - ASSUME(SampleSize > 0); - ASSUME(increment > 0); - - ALCdevice *Device{Context->Device}; - const ALsizei NumSends{Device->NumAuxSends}; - const ALsizei IrSize{Device->mHrtf ? Device->mHrtf->irSize : 0}; - - ASSUME(NumSends >= 0); - ASSUME(IrSize >= 0); - - ResamplerFunc Resample{(increment == FRACTIONONE && DataPosFrac == 0) ? - Resample_<CopyTag,CTag> : voice->mResampler}; - - ALsizei Counter{(voice->mFlags&VOICE_IS_FADING) ? SamplesToDo : 0}; - if(!Counter) - { - /* No fading, just overwrite the old/current params. */ - for(ALsizei chan{0};chan < NumChannels;chan++) - { - ALvoice::ChannelData &chandata = voice->mChans[chan]; - DirectParams &parms = chandata.mDryParams; - if(!(voice->mFlags&VOICE_HAS_HRTF)) - std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target), - std::begin(parms.Gains.Current)); - else - parms.Hrtf.Old = parms.Hrtf.Target; - for(ALsizei send{0};send < NumSends;++send) - { - if(voice->mSend[send].Buffer.empty()) - continue; - - SendParams &parms = chandata.mWetParams[send]; - std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target), - std::begin(parms.Gains.Current)); - } - } - } - else if((voice->mFlags&VOICE_HAS_HRTF)) - { - for(ALsizei chan{0};chan < NumChannels;chan++) - { - DirectParams &parms = voice->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; - } - } - } - - ALsizei buffers_done{0}; - ALsizei OutPos{0}; - do { - /* Figure out how many buffer samples will be needed */ - ALsizei DstBufferSize{SamplesToDo - OutPos}; - - /* Calculate the last written dst sample pos. */ - int64_t DataSize64{DstBufferSize - 1}; - /* Calculate the last read src sample pos. */ - DataSize64 = (DataSize64*increment + DataPosFrac) >> FRACTIONBITS; - /* +1 to get the src sample count, include padding. */ - DataSize64 += 1 + MAX_RESAMPLE_PADDING*2; - - auto SrcBufferSize = static_cast<ALuint>( - mini64(DataSize64, BUFFERSIZE + MAX_RESAMPLE_PADDING*2 + 1)); - if(SrcBufferSize > BUFFERSIZE + MAX_RESAMPLE_PADDING*2) - { - SrcBufferSize = BUFFERSIZE + MAX_RESAMPLE_PADDING*2; - /* If the source buffer got saturated, we can't fill the desired - * dst size. Figure out how many samples we can actually mix from - * this. - */ - DataSize64 = SrcBufferSize - MAX_RESAMPLE_PADDING*2; - DataSize64 = ((DataSize64<<FRACTIONBITS) - DataPosFrac + increment-1) / increment; - DstBufferSize = static_cast<ALsizei>(mini64(DataSize64, DstBufferSize)); - - /* Some mixers like having a multiple of 4, so try to give that - * unless this is the last update. - */ - if(DstBufferSize < SamplesToDo-OutPos) - DstBufferSize &= ~3; - } - - for(ALsizei chan{0};chan < NumChannels;chan++) - { - ALvoice::ChannelData &chandata = voice->mChans[chan]; - const al::span<ALfloat> SrcData{Device->SourceData, SrcBufferSize}; - - /* Load the previous samples into the source data first, and clear the rest. */ - auto srciter = std::copy_n(chandata.mPrevSamples.begin(), MAX_RESAMPLE_PADDING, - SrcData.begin()); - std::fill(srciter, SrcData.end(), 0.0f); - - if(UNLIKELY(!BufferListItem)) - srciter = std::copy(chandata.mPrevSamples.begin()+MAX_RESAMPLE_PADDING, - 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(&voice->mResampleState, - &SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment, - Device->ResampledData, DstBufferSize)}; - if((voice->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. */ - { - DirectParams &parms = chandata.mDryParams; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, - Device->FilteredData, ResampledData, DstBufferSize, - voice->mDirect.FilterType)}; - - if((voice->mFlags&VOICE_HAS_HRTF)) - { - const int OutLIdx{GetChannelIdxByName(Device->RealOut, FrontLeft)}; - const int OutRIdx{GetChannelIdxByName(Device->RealOut, FrontRight)}; - ASSUME(OutLIdx >= 0 && OutRIdx >= 0); - - auto &HrtfSamples = Device->HrtfSourceData; - auto &AccumSamples = Device->HrtfAccumData; - const ALfloat TargetGain{UNLIKELY(vstate == ALvoice::Stopping) ? 0.0f : - parms.Hrtf.Target.Gain}; - ALsizei fademix{0}; - - /* Copy the HRTF history and new input samples into a temp - * buffer. - */ - auto src_iter = std::copy(parms.Hrtf.State.History.begin(), - parms.Hrtf.State.History.end(), std::begin(HrtfSamples)); - std::copy_n(samples, DstBufferSize, src_iter); - /* Copy the last used samples back into the history buffer - * for later. - */ - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, - parms.Hrtf.State.History.size(), parms.Hrtf.State.History.begin()); - - /* Copy the current filtered values being accumulated into - * the temp buffer. - */ - auto accum_iter = std::copy_n(parms.Hrtf.State.Values.begin(), - parms.Hrtf.State.Values.size(), std::begin(AccumSamples)); - - /* Clear the accumulation buffer that will start getting - * filled in. - */ - std::fill_n(accum_iter, DstBufferSize, float2{}); - - /* If fading, the old gain is not silence, and this is the - * first mixing pass, fade between the IRs. - */ - if(Counter && (parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD) && OutPos == 0) - { - fademix = mini(DstBufferSize, 128); - - ALfloat gain{TargetGain}; - - /* The new coefficients need to fade in completely - * since they're replacing the old ones. To keep the - * gain fading consistent, interpolate between the old - * and new target gains given how much of the fade time - * this mix handles. - */ - if(LIKELY(Counter > fademix)) - { - const ALfloat a{static_cast<ALfloat>(fademix) / - static_cast<ALfloat>(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<ALfloat>(fademix); - - MixHrtfBlendSamples(voice->mDirect.Buffer[OutLIdx], - voice->mDirect.Buffer[OutRIdx], HrtfSamples, AccumSamples, OutPos, - IrSize, &parms.Hrtf.Old, &hrtfparams, fademix); - /* Update the old parameters with the result. */ - parms.Hrtf.Old = parms.Hrtf.Target; - if(fademix < Counter) - parms.Hrtf.Old.Gain = hrtfparams.Gain; - else - parms.Hrtf.Old.Gain = TargetGain; - } - - if(LIKELY(fademix < DstBufferSize)) - { - const ALsizei todo{DstBufferSize - fademix}; - ALfloat gain{TargetGain}; - - /* Interpolate the target gain if the gain fading lasts - * longer than this mix. - */ - if(Counter > DstBufferSize) - { - const ALfloat a{static_cast<ALfloat>(todo) / - static_cast<ALfloat>(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<ALfloat>(todo); - MixHrtfSamples(voice->mDirect.Buffer[OutLIdx], - voice->mDirect.Buffer[OutRIdx], HrtfSamples+fademix, - AccumSamples+fademix, OutPos+fademix, IrSize, &hrtfparams, todo); - /* Store the interpolated gain or the final target gain - * depending if the fade is done. - */ - if(DstBufferSize < Counter) - parms.Hrtf.Old.Gain = gain; - else - parms.Hrtf.Old.Gain = TargetGain; - } - - /* Copy the new in-progress accumulation values back for - * the next mix. - */ - std::copy_n(std::begin(AccumSamples) + DstBufferSize, - parms.Hrtf.State.Values.size(), parms.Hrtf.State.Values.begin()); - } - else if((voice->mFlags&VOICE_HAS_NFC)) - { - const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget : parms.Gains.Target}; - - const size_t outcount{Device->NumChannelsPerOrder[0]}; - MixSamples(samples, voice->mDirect.Buffer.first(outcount), parms.Gains.Current, - TargetGains, Counter, OutPos, DstBufferSize); - - ALfloat (&nfcsamples)[BUFFERSIZE] = Device->NfcSampleData; - size_t chanoffset{outcount}; - using FilterProc = void (NfcFilter::*)(float*,const float*,int); - auto apply_nfc = [voice,&parms,samples,TargetGains,DstBufferSize,Counter,OutPos,&chanoffset,&nfcsamples](const FilterProc process, const size_t outcount) -> void - { - if(outcount < 1) return; - (parms.NFCtrlFilter.*process)(nfcsamples, samples, DstBufferSize); - MixSamples(nfcsamples, voice->mDirect.Buffer.subspan(chanoffset, outcount), - parms.Gains.Current+chanoffset, TargetGains+chanoffset, Counter, - OutPos, DstBufferSize); - chanoffset += outcount; - }; - apply_nfc(&NfcFilter::process1, Device->NumChannelsPerOrder[1]); - apply_nfc(&NfcFilter::process2, Device->NumChannelsPerOrder[2]); - apply_nfc(&NfcFilter::process3, Device->NumChannelsPerOrder[3]); - } - else - { - const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget : parms.Gains.Target}; - MixSamples(samples, voice->mDirect.Buffer, parms.Gains.Current, TargetGains, - Counter, OutPos, DstBufferSize); - } - } - - ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData; - for(ALsizei send{0};send < NumSends;++send) - { - if(voice->mSend[send].Buffer.empty()) - continue; - - SendParams &parms = chandata.mWetParams[send]; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, - FilterBuf, ResampledData, DstBufferSize, voice->mSend[send].FilterType)}; - - const ALfloat *TargetGains{UNLIKELY(vstate==ALvoice::Stopping) ? SilentTarget : - parms.Gains.Target}; - MixSamples(samples, voice->mSend[send].Buffer, parms.Gains.Current, TargetGains, - Counter, OutPos, DstBufferSize); - }; - } - /* Update positions */ - DataPosFrac += increment*DstBufferSize; - DataPosInt += DataPosFrac>>FRACTIONBITS; - DataPosFrac &= FRACTIONMASK; - - OutPos += DstBufferSize; - Counter = maxi(DstBufferSize, Counter) - DstBufferSize; - - if(UNLIKELY(!BufferListItem)) - { - /* Do nothing extra when there's no buffers. */ - } - else if(isstatic) - { - if(BufferLoopItem) - { - /* Handle looping static source */ - const ALbuffer *Buffer{BufferListItem->buffers[0]}; - const ALsizei LoopStart{Buffer->LoopStart}; - const ALsizei LoopEnd{Buffer->LoopEnd}; - if(DataPosInt >= LoopEnd) - { - assert(LoopEnd > LoopStart); - DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; - } - } - else - { - /* Handle non-looping static source */ - if(DataPosInt >= BufferListItem->max_samples) - { - if(LIKELY(vstate == ALvoice::Playing)) - vstate = ALvoice::Stopped; - BufferListItem = nullptr; - break; - } - } - } - else while(1) - { - /* Handle streaming source */ - if(BufferListItem->max_samples > DataPosInt) - break; - - DataPosInt -= BufferListItem->max_samples; - - buffers_done += BufferListItem->num_buffers; - BufferListItem = BufferListItem->next.load(std::memory_order_relaxed); - if(!BufferListItem && !(BufferListItem=BufferLoopItem)) - { - if(LIKELY(vstate == ALvoice::Playing)) - vstate = ALvoice::Stopped; - break; - } - } - } while(OutPos < SamplesToDo); - - voice->mFlags |= VOICE_IS_FADING; - - /* Don't update positions and buffers if we were stopping. */ - if(UNLIKELY(vstate == ALvoice::Stopping)) - { - voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release); - return; - } - - /* Update voice info */ - voice->mPosition.store(DataPosInt, std::memory_order_relaxed); - voice->mPositionFrac.store(DataPosFrac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed); - if(vstate == ALvoice::Stopped) - { - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - } - std::atomic_thread_fence(std::memory_order_release); - - /* Send any events now, after the position/buffer info was updated. */ - ALbitfieldSOFT enabledevt{Context->EnabledEvts.load(std::memory_order_acquire)}; - if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted)) - { - RingBuffer *ring{Context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len > 0) - { - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}}; - evt->u.bufcomp.id = SourceID; - evt->u.bufcomp.count = buffers_done; - ring->writeAdvance(1); - Context->EventSem.post(); - } - } - - if(vstate == ALvoice::Stopped) - { - /* If the voice just ended, set it to Stopping so the next render - * ensures any residual noise fades to 0 amplitude. - */ - voice->mPlayState.store(ALvoice::Stopping, std::memory_order_release); - SendSourceStoppedEvent(Context, SourceID); - } -} diff --git a/Alc/panning.cpp b/Alc/panning.cpp deleted file mode 100644 index 3a67e33a..00000000 --- a/Alc/panning.cpp +++ /dev/null @@ -1,964 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2010 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 <cmath> -#include <cstdlib> -#include <cstring> -#include <cctype> -#include <cassert> - -#include <cmath> -#include <chrono> -#include <numeric> -#include <algorithm> -#include <functional> - -#include "alcmain.h" -#include "alAuxEffectSlot.h" -#include "alu.h" -#include "alconfig.h" -#include "ambdec.h" -#include "bformatdec.h" -#include "filters/splitter.h" -#include "uhjfilter.h" -#include "bs2b.h" - -#include "alspan.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<int,MAX_AMBI_CHANNELS> AmbiIndex::FromFuMa; -constexpr std::array<int,MAX_AMBI_CHANNELS> AmbiIndex::FromACN; -constexpr std::array<int,MAX_AMBI2D_CHANNELS> AmbiIndex::From2D; -constexpr std::array<int,MAX_AMBI_CHANNELS> AmbiIndex::From3D; - - -namespace { - -using namespace std::placeholders; -using std::chrono::seconds; -using std::chrono::nanoseconds; - -inline const char *GetLabelFromChannel(Channel channel) -{ - switch(channel) - { - case FrontLeft: return "front-left"; - case FrontRight: return "front-right"; - case FrontCenter: return "front-center"; - case LFE: return "lfe"; - case BackLeft: return "back-left"; - case BackRight: return "back-right"; - case BackCenter: return "back-center"; - 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 MaxChannels: break; - } - return "(unknown)"; -} - - -void AllocChannels(ALCdevice *device, const ALuint main_chans, const ALuint real_chans) -{ - TRACE("Channel config, Main: %u, Real: %u\n", main_chans, real_chans); - - /* Allocate extra channels for any post-filter output. */ - const ALuint num_chans{main_chans + real_chans}; - - TRACE("Allocating %u 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()}; - - device->Dry.Buffer = buffer.first(main_chans); - buffer = buffer.subspan(main_chans); - if(real_chans != 0) - { - device->RealOut.Buffer = buffer.first(real_chans); - buffer = buffer.subspan(real_chans); - } - else - device->RealOut.Buffer = device->Dry.Buffer; -} - - -struct ChannelMap { - Channel ChanName; - ALfloat Config[MAX_AMBI2D_CHANNELS]; -}; - -bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - auto map_spkr = [device](const AmbDecConf::SpeakerConf &speaker) -> ALsizei - { - /* NOTE: AmbDec does not define any standard speaker names, however - * for this to work we have to by able to find the output channel - * the speaker definition corresponds to. Therefore, OpenAL Soft - * requires these channel labels to be recognized: - * - * LF = Front left - * RF = Front right - * LS = Side left - * RS = Side right - * LB = Back left - * RB = Back right - * CE = Front center - * CB = Back center - * - * Additionally, surround51 will acknowledge back speakers for side - * channels, and surround51rear will acknowledge side speakers for - * back channels, to avoid issues with an ambdec expecting 5.1 to - * use the side channels when the device is configured for back, - * and vice-versa. - */ - Channel ch{}; - if(speaker.Name == "LF") - ch = FrontLeft; - else if(speaker.Name == "RF") - ch = FrontRight; - else if(speaker.Name == "CE") - ch = FrontCenter; - else if(speaker.Name == "LS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackLeft; - else - ch = SideLeft; - } - else if(speaker.Name == "RS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackRight; - else - ch = SideRight; - } - else if(speaker.Name == "LB") - { - if(device->FmtChans == DevFmtX51) - ch = SideLeft; - else - ch = BackLeft; - } - else if(speaker.Name == "RB") - { - if(device->FmtChans == DevFmtX51) - ch = SideRight; - else - ch = BackRight; - } - else if(speaker.Name == "CB") - ch = BackCenter; - else - { - const char *name{speaker.Name.c_str()}; - unsigned int n; - char c; - - 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 -1; - } - } - const int chidx{GetChannelIdxByName(device->RealOut, ch)}; - if(chidx == -1) - ERR("Failed to lookup AmbDec speaker label %s\n", speaker.Name.c_str()); - return chidx; - }; - std::transform(conf->Speakers.begin(), conf->Speakers.end(), std::begin(speakermap), map_spkr); - /* Return success if no invalid entries are found. */ - auto speakermap_end = std::begin(speakermap) + conf->Speakers.size(); - return std::find(std::begin(speakermap), speakermap_end, -1) == speakermap_end; -} - - -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 } }, -}; - -void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALsizei order, - const al::span<const ALuint,MAX_AMBI_ORDER+1> chans_per_order) -{ - /* 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)) - 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, - std::begin(device->NumChannelsPerOrder)); - std::fill(iter, std::end(device->NumChannelsPerOrder), 0u); -} - -void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - 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 char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "distance-comp", 1) || !(maxdist > 0.0f)) - return; - - const auto distSampleScale = static_cast<ALfloat>(device->Frequency)/SPEEDOFSOUNDMETRESPERSEC; - const auto ChanDelay = device->ChannelDelay.as_span(); - size_t total{0u}; - for(size_t i{0u};i < conf->Speakers.size();i++) - { - const AmbDecConf::SpeakerConf &speaker = conf->Speakers[i]; - const ALsizei chan{speakermap[i]}; - - /* Distance compensation only delays in steps of the sample rate. This - * is a bit less accurate since the delay time falls to the nearest - * sample time, but it's far simpler as it doesn't have to deal with - * 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}) - { - 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}; - } - - ChanDelay[chan].Length = static_cast<ALsizei>(delay); - ChanDelay[chan].Gain = speaker.Distance / maxdist; - TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan, - speaker.Name.c_str(), ChanDelay[chan].Length, ChanDelay[chan].Gain); - - /* Round up to the next 4th sample, so each channel buffer starts - * 16-byte aligned. - */ - total += RoundUp(ChanDelay[chan].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 - { - DistanceComp::DistData ret{cur}; - ret.Buffer = last.Buffer + RoundUp(last.Length, 4); - return ret; - }; - std::partial_sum(ChanDelay.begin(), ChanDelay.end(), ChanDelay.begin(), set_bufptr); - } -} - - -auto GetAmbiScales(AmbiNorm scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>& -{ - if(scaletype == AmbiNorm::FuMa) return AmbiScale::FromFuMa; - if(scaletype == AmbiNorm::SN3D) return AmbiScale::FromSN3D; - return AmbiScale::FromN3D; -} - -auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<int,MAX_AMBI_CHANNELS>& -{ - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa; - return AmbiIndex::FromACN; -} - - -void InitPanning(ALCdevice *device) -{ - al::span<const ChannelMap> chanmap; - ALuint coeffcount{}; - - switch(device->FmtChans) - { - case DevFmtMono: - chanmap = MonoCfg; - coeffcount = 1; - break; - - 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; - - case DevFmtX61: - chanmap = X61Cfg; - coeffcount = 5; - break; - - case DevFmtX71: - chanmap = X71Cfg; - coeffcount = 7; - break; - - case DevFmtAmbi3D: - break; - } - - if(device->FmtChans == DevFmtAmbi3D) - { - const char *devname{device->DeviceName.c_str()}; - const std::array<int,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 ALsizei &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) - { - static constexpr ALuint chans_per_order[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; - InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC, device->mAmbiOrder, - chans_per_order); - } - } - else - { - ChannelDec chancoeffs[MAX_OUTPUT_CHANNELS]{}; - ALsizei idxmap[MAX_OUTPUT_CHANNELS]{}; - for(size_t i{0u};i < chanmap.size();++i) - { - const ALint idx{GetChannelIdxByName(device->RealOut, chanmap[i].ChanName)}; - if(idx < 0) - { - ERR("Failed to find %s channel in device\n", - GetLabelFromChannel(chanmap[i].ChanName)); - continue; - } - 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 = static_cast<ALsizei>((coeffcount-1) / 2); - - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+coeffcount, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - 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); - } -} - -void InitCustomPanning(ALCdevice *device, bool hqdec, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - static constexpr ALuint chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 }; - static constexpr ALuint chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 }; - - if(!hqdec && conf->FreqBands != 1) - ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n", - conf->XOverFreq); - - ALsizei order{(conf->ChanMask > AMBI_2ORDER_MASK) ? 3 : - (conf->ChanMask > AMBI_1ORDER_MASK) ? 2 : 1}; - device->mAmbiOrder = order; - - 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 ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - } - else - { - count = static_cast<ALuint>(Ambi2DChannelsFromOrder(order)); - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - } - 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(), float{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); -} - -void InitHrtfPanning(ALCdevice *device) -{ - /* NOTE: In degrees, and azimuth goes clockwise. */ - static constexpr AngularPoint AmbiPoints[]{ - { 35.264390f, -45.000000f }, - { 35.264390f, 45.000000f }, - { 35.264390f, 135.000000f }, - { 35.264390f, -135.000000f }, - { -35.264390f, -45.000000f }, - { -35.264390f, 45.000000f }, - { -35.264390f, 135.000000f }, - { -35.264390f, -135.000000f }, - { 0.000000f, -20.905157f }, - { 0.000000f, 20.905157f }, - { 0.000000f, 159.094843f }, - { 0.000000f, -159.094843f }, - { 20.905157f, -90.000000f }, - { -20.905157f, -90.000000f }, - { -20.905157f, 90.000000f }, - { 20.905157f, 90.000000f }, - { 69.094843f, 0.000000f }, - { -69.094843f, 0.000000f }, - { -69.094843f, 180.000000f }, - { 69.094843f, 180.000000f }, - }; - static constexpr ALfloat AmbiMatrix[][MAX_AMBI_CHANNELS]{ - { 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, 6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, 6.33865691e-02f, 1.01126676e-01f, -7.36485380e-02f, -1.09260065e-02f, 7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, -6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, -6.33865691e-02f, -1.01126676e-01f, -7.36485380e-02f, -1.09260065e-02f, 7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, 6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, 6.33865691e-02f, -1.01126676e-01f, -7.36485380e-02f, 1.09260065e-02f, 7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, -6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, -6.33865691e-02f, 1.01126676e-01f, -7.36485380e-02f, 1.09260065e-02f, 7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, 6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, -6.33865691e-02f, 1.01126676e-01f, 7.36485380e-02f, -1.09260065e-02f, -7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, -6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, 6.33865691e-02f, -1.01126676e-01f, 7.36485380e-02f, -1.09260065e-02f, -7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, 6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, -6.33865691e-02f, -1.01126676e-01f, 7.36485380e-02f, 1.09260065e-02f, -7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, -6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, 6.33865691e-02f, 1.01126676e-01f, 7.36485380e-02f, 1.09260065e-02f, -7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 3.09016994e-02f, 0.00000000e+00f, 8.09016994e-02f, 6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, 7.76323754e-02f, 0.00000000e+00f, -1.49775925e-01f, 0.00000000e+00f, -2.95083663e-02f, 0.00000000e+00f, 7.76323754e-02f }, - { 5.00000000e-02f, -3.09016994e-02f, 0.00000000e+00f, 8.09016994e-02f, -6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, -7.76323754e-02f, 0.00000000e+00f, 1.49775925e-01f, 0.00000000e+00f, -2.95083663e-02f, 0.00000000e+00f, 7.76323754e-02f }, - { 5.00000000e-02f, -3.09016994e-02f, 0.00000000e+00f, -8.09016994e-02f, 6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, -7.76323754e-02f, 0.00000000e+00f, 1.49775925e-01f, 0.00000000e+00f, 2.95083663e-02f, 0.00000000e+00f, -7.76323754e-02f }, - { 5.00000000e-02f, 3.09016994e-02f, 0.00000000e+00f, -8.09016994e-02f, -6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, 7.76323754e-02f, 0.00000000e+00f, -1.49775925e-01f, 0.00000000e+00f, 2.95083663e-02f, 0.00000000e+00f, -7.76323754e-02f }, - { 5.00000000e-02f, 8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, -4.79794466e-02f, 0.00000000e+00f, -6.77901327e-02f, 3.03448665e-02f, 0.00000000e+00f, -1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, 8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, -6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, -4.79794466e-02f, 0.00000000e+00f, -6.77901327e-02f, -3.03448665e-02f, 0.00000000e+00f, 1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, -8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, 4.79794466e-02f, 0.00000000e+00f, 6.77901327e-02f, -3.03448665e-02f, 0.00000000e+00f, 1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, -8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, -6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, 4.79794466e-02f, 0.00000000e+00f, 6.77901327e-02f, 3.03448665e-02f, 0.00000000e+00f, -1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, 0.00000000e+00f, 8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, 6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, 7.94438918e-02f, 1.12611206e-01f, -2.42115150e-02f, 1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, -8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, -6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -7.94438918e-02f, 1.12611206e-01f, 2.42115150e-02f, 1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, -8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, 6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -7.94438918e-02f, -1.12611206e-01f, 2.42115150e-02f, -1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, 8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, -6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, 7.94438918e-02f, -1.12611206e-01f, -2.42115150e-02f, -1.25611822e-01f } - }; - static constexpr ALfloat AmbiOrderHFGain1O[MAX_AMBI_ORDER+1]{ - 3.16227766e+00f, 1.82574186e+00f - }, AmbiOrderHFGain2O[MAX_AMBI_ORDER+1]{ - 2.35702260e+00f, 1.82574186e+00f, 9.42809042e-01f - }, AmbiOrderHFGain3O[MAX_AMBI_ORDER+1]{ - 1.86508671e+00f, 1.60609389e+00f, 1.14205530e+00f, 5.68379553e-01f - }; - static constexpr ALuint ChansPerOrder[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; - const ALfloat *AmbiOrderHFGain{AmbiOrderHFGain1O}; - - static_assert(al::size(AmbiPoints) == al::size(AmbiMatrix), "Ambisonic HRTF mismatch"); - - /* Don't bother with HOA when using full HRTF rendering. Nothing needs it, - * and it eases the CPU/memory load. - */ - device->mRenderMode = HrtfRender; - ALsizei ambi_order{1}; - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf-mode")) - { - const char *mode{modeopt->c_str()}; - if(strcasecmp(mode, "basic") == 0) - { - ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2"); - mode = "ambi2"; - } - - if(strcasecmp(mode, "full") == 0) - device->mRenderMode = HrtfRender; - else if(strcasecmp(mode, "ambi1") == 0) - { - device->mRenderMode = NormalRender; - ambi_order = 1; - } - else if(strcasecmp(mode, "ambi2") == 0) - { - device->mRenderMode = NormalRender; - ambi_order = 2; - } - else if(strcasecmp(mode, "ambi3") == 0) - { - device->mRenderMode = NormalRender; - ambi_order = 3; - } - else - ERR("Unexpected hrtf-mode: %s\n", mode); - } - TRACE("%s HRTF rendering enabled, using \"%s\"\n", - (device->mRenderMode == HrtfRender) ? "Full" : - (ambi_order >= 3) ? "Third-Order" : - (ambi_order == 2) ? "Second-Order" : - (ambi_order == 1) ? "First-Order" : "Unknown", - device->HrtfName.c_str()); - - if(ambi_order >= 3) - AmbiOrderHFGain = AmbiOrderHFGain3O; - else if(ambi_order == 2) - AmbiOrderHFGain = AmbiOrderHFGain2O; - else if(ambi_order == 1) - AmbiOrderHFGain = AmbiOrderHFGain1O; - device->mAmbiOrder = ambi_order; - - const size_t count{AmbiChannelsFromOrder(ambi_order)}; - device->mHrtfState = DirectHrtfState::Create(count); - - std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - AllocChannels(device, static_cast<ALuint>(count), device->channelsFromFmt()); - - BuildBFormatHrtf(device->mHrtf, device->mHrtfState.get(), static_cast<ALuint>(count), - AmbiPoints, AmbiMatrix, al::size(AmbiPoints), AmbiOrderHFGain); - - HrtfEntry *Hrtf{device->mHrtf}; - InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, ChansPerOrder); -} - -void InitUhjPanning(ALCdevice *device) -{ - /* UHJ is always 2D first-order. */ - static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; - - device->mAmbiOrder = 1; - - auto acnmap_end = AmbiIndex::FromFuMa.begin() + count; - std::transform(AmbiIndex::FromFuMa.begin(), acnmap_end, std::begin(device->Dry.AmbiMap), - [](const ALsizei &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromFuMa[acn], acn}; } - ); - AllocChannels(device, ALuint{count}, device->channelsFromFmt()); -} - -} // namespace - -void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq) -{ - /* Hold the HRTF the device last used, in case it's used again. */ - HrtfEntry *old_hrtf{device->mHrtf}; - - device->mHrtfState = nullptr; - device->mHrtf = nullptr; - device->HrtfName.clear(); - device->mRenderMode = NormalRender; - - 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; - - 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; - } - - const char *devname{device->DeviceName.c_str()}; - ALsizei speakermap[MAX_OUTPUT_CHANNELS]; - AmbDecConf *pconf{nullptr}; - AmbDecConf conf{}; - if(layout) - { - if(auto decopt = ConfigValueStr(devname, "decoder", layout)) - { - 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; - } - } - - if(!pconf) - InitPanning(device); - else - { - int hqdec{GetConfigValueBool(devname, "decoder", "hq-mode", 0)}; - InitCustomPanning(device, !!hqdec, pconf, speakermap); - } - if(device->AmbiDecoder) - device->PostProcess = ProcessAmbiDec; - return; - } - - bool headphones{device->IsHeadphones != AL_FALSE}; - if(device->Type != Loopback) - { - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-mode")) - { - const char *mode{modeopt->c_str()}; - if(strcasecmp(mode, "headphones") == 0) - headphones = true; - else if(strcasecmp(mode, "speakers") == 0) - headphones = false; - else if(strcasecmp(mode, "auto") != 0) - ERR("Unexpected stereo-mode: %s\n", mode); - } - } - - if(hrtf_userreq == Hrtf_Default) - { - bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) || - (hrtf_appreq == Hrtf_Enable); - if(!usehrtf) goto no_hrtf; - - device->HrtfStatus = ALC_HRTF_ENABLED_SOFT; - if(headphones && hrtf_appreq != Hrtf_Disable) - device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT; - } - else - { - if(hrtf_userreq != Hrtf_Enable) - { - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_DENIED_SOFT; - goto no_hrtf; - } - device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT; - } - - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - - if(hrtf_id >= 0 && static_cast<size_t>(hrtf_id) < device->HrtfList.size()) - { - const EnumeratedHrtf &entry = device->HrtfList[hrtf_id]; - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(hrtf && hrtf->sampleRate == device->Frequency) - { - device->mHrtf = hrtf; - device->HrtfName = entry.name; - } - else if(hrtf) - hrtf->DecRef(); - } - - if(!device->mHrtf) - { - auto find_hrtf = [device](const EnumeratedHrtf &entry) -> bool - { - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(!hrtf) return false; - if(hrtf->sampleRate != device->Frequency) - { - hrtf->DecRef(); - return false; - } - device->mHrtf = hrtf; - device->HrtfName = entry.name; - return true; - }; - std::find_if(device->HrtfList.cbegin(), device->HrtfList.cend(), find_hrtf); - } - - if(device->mHrtf) - { - if(old_hrtf) - old_hrtf->DecRef(); - old_hrtf = nullptr; - - InitHrtfPanning(device); - device->PostProcess = ProcessHrtf; - 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) - { - if(auto cflevopt = ConfigValueInt(device->DeviceName.c_str(), nullptr, "cf_level")) - { - if(*cflevopt > 0 && *cflevopt <= 6) - { - device->Bs2b = al::make_unique<bs2b>(); - bs2b_set_params(device->Bs2b.get(), *cflevopt, device->Frequency); - TRACE("BS2B enabled\n"); - InitPanning(device); - device->PostProcess = ProcessBs2b; - return; - } - } - } - - if(auto encopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-encoding")) - { - const char *mode{encopt->c_str()}; - if(strcasecmp(mode, "uhj") == 0) - device->mRenderMode = NormalRender; - else if(strcasecmp(mode, "panpot") != 0) - ERR("Unexpected stereo-encoding: %s\n", mode); - } - if(device->mRenderMode == NormalRender) - { - device->Uhj_Encoder = al::make_unique<Uhj2Encoder>(); - TRACE("UHJ enabled\n"); - InitUhjPanning(device); - device->PostProcess = ProcessUhj; - return; - } - - TRACE("Stereo rendering\n"); - InitPanning(device); - device->PostProcess = ProcessAmbiDec; -} - - -void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device) -{ - const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - slot->MixBuffer.resize(count); - slot->MixBuffer.shrink_to_fit(); - - auto acnmap_end = AmbiIndex::From3D.begin() + count; - auto iter = std::transform(AmbiIndex::From3D.begin(), acnmap_end, slot->Wet.AmbiMap.begin(), - [](const ALsizei &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f, acn}; } - ); - std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); - slot->Wet.Buffer = {slot->MixBuffer.data(), slot->MixBuffer.size()}; -} - - -void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread, - ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) -{ - /* Zeroth-order */ - coeffs[0] = 1.0f; /* ACN 0 = 1 */ - /* First-order */ - coeffs[1] = 1.732050808f * y; /* ACN 1 = sqrt(3) * Y */ - coeffs[2] = 1.732050808f * z; /* ACN 2 = sqrt(3) * Z */ - coeffs[3] = 1.732050808f * x; /* ACN 3 = sqrt(3) * X */ - /* Second-order */ - coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */ - coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */ - coeffs[6] = 1.118033989f * (z*z*3.0f - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ - coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */ - coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ - /* Third-order */ - coeffs[9] = 2.091650066f * y * (x*x*3.0f - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ - coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */ - coeffs[11] = 1.620185175f * y * (z*z*5.0f - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ - coeffs[12] = 1.322875656f * z * (z*z*5.0f - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ - coeffs[13] = 1.620185175f * x * (z*z*5.0f - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ - coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ - coeffs[15] = 2.091650066f * x * (x*x - y*y*3.0f); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ - /* Fourth-order */ - /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ - /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ - /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ - /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ - /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ - /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ - /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ - /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ - /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ - - if(spread > 0.0f) - { - /* Implement the spread by using a spherical source that subtends the - * angle spread. See: - * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 - * - * When adjusted for N3D normalization instead of SN3D, these - * calculations are: - * - * ZH0 = -sqrt(pi) * (-1+ca); - * ZH1 = 0.5*sqrt(pi) * sa*sa; - * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); - * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); - * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); - * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); - * - * The gain of the source is compensated for size, so that the - * loudness doesn't depend on the spread. Thus: - * - * ZH0 = 1.0f; - * ZH1 = 0.5f * (ca+1.0f); - * ZH2 = 0.5f * (ca+1.0f)*ca; - * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); - * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; - * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); - */ - ALfloat ca = std::cos(spread * 0.5f); - /* Increase the source volume by up to +3dB for a full spread. */ - ALfloat scale = std::sqrt(1.0f + spread/al::MathDefs<float>::Tau()); - - ALfloat ZH0_norm = scale; - ALfloat ZH1_norm = 0.5f * (ca+1.f) * scale; - ALfloat ZH2_norm = 0.5f * (ca+1.f)*ca * scale; - ALfloat ZH3_norm = 0.125f * (ca+1.f)*(5.f*ca*ca-1.f) * scale; - - /* Zeroth-order */ - coeffs[0] *= ZH0_norm; - /* First-order */ - coeffs[1] *= ZH1_norm; - coeffs[2] *= ZH1_norm; - coeffs[3] *= ZH1_norm; - /* Second-order */ - coeffs[4] *= ZH2_norm; - coeffs[5] *= ZH2_norm; - coeffs[6] *= ZH2_norm; - coeffs[7] *= ZH2_norm; - coeffs[8] *= ZH2_norm; - /* Third-order */ - coeffs[9] *= ZH3_norm; - coeffs[10] *= ZH3_norm; - coeffs[11] *= ZH3_norm; - coeffs[12] *= ZH3_norm; - coeffs[13] *= ZH3_norm; - coeffs[14] *= ZH3_norm; - coeffs[15] *= ZH3_norm; - } -} - -void ComputePanGains(const MixParams *mix, const ALfloat *RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS]) -{ - auto ambimap = mix->AmbiMap.cbegin(); - - auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), std::begin(gains), - [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> ALfloat - { - ASSUME(chanmap.Index >= 0); - return chanmap.Scale * coeffs[chanmap.Index] * ingain; - } - ); - std::fill(iter, std::end(gains), 0.0f); -} diff --git a/Alc/ringbuffer.cpp b/Alc/ringbuffer.cpp deleted file mode 100644 index 6ef576a5..00000000 --- a/Alc/ringbuffer.cpp +++ /dev/null @@ -1,253 +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 <cstring> -#include <cstdlib> -#include <climits> - -#include <algorithm> - -#include "ringbuffer.h" -#include "atomic.h" -#include "threads.h" -#include "almalloc.h" -#include "compat.h" - - -RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes) -{ - 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) return nullptr; - - const size_t bufbytes{power_of_two * elem_sz}; - RingBufferPtr rb{new (al_calloc(16, sizeof(*rb) + 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 84139b66..00000000 --- a/Alc/ringbuffer.h +++ /dev/null @@ -1,99 +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} { } - RingBuffer(const RingBuffer&) = delete; - RingBuffer& operator=(const RingBuffer&) = delete; - - /** 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_PLACE_NEWDEL() -}; -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 55999647..00000000 --- a/Alc/uhjfilter.cpp +++ /dev/null @@ -1,131 +0,0 @@ - -#include "config.h" - -#include "uhjfilter.h" - -#include <algorithm> - -#include "alu.h" - -namespace { - -/* This is the maximum number of samples processed for each inner loop - * iteration. */ -#define MAX_UPDATE_SAMPLES 128 - - -constexpr ALfloat Filter1CoeffSqr[4] = { - 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f -}; -constexpr ALfloat Filter2CoeffSqr[4] = { - 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f -}; - -void allpass_process(AllPassState *state, ALfloat *dst, const ALfloat *src, const ALfloat aa, ALsizei todo) -{ - ALfloat z1{state->z[0]}; - ALfloat z2{state->z[1]}; - auto proc_sample = [aa,&z1,&z2](ALfloat input) noexcept -> ALfloat - { - ALfloat output = input*aa + z1; - z1 = z2; z2 = output*aa - input; - return output; - }; - std::transform(src, src+todo, dst, proc_sample); - state->z[0] = z1; - state->z[1] = z2; -} - -} // namespace - - -/* NOTE: There seems to be a bit of an inconsistency in how this encoding is - * supposed to work. Some references, such as - * - * http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html - * - * specify a pre-scaling of sqrt(2) on the W channel input, while other - * references, such as - * - * https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D - * and - * https://wiki.xiph.org/Ambisonics#UHJ_format - * - * do not. The sqrt(2) scaling is in line with B-Format decoder coefficients - * which include such a scaling for the W channel input, however the original - * source for this equation is a 1985 paper by Michael Gerzon, which does not - * apparently include the scaling. Applying the extra scaling creates a louder - * result with a narrower stereo image compared to not scaling, and I don't - * know which is the intended result. - */ - -void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, FloatBufferLine *InSamples, const ALsizei 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(ALsizei base{0};base < SamplesToDo;) - { - const ALsizei todo{mini(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(ALsizei 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(ALsizei 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(ALsizei i{1};i < todo;i++) - S[i] = temp[i-1]; - mLastWX = temp[todo-1]; - - /* Left = (S + D)/2.0 */ - ALfloat *RESTRICT left = al::assume_aligned<16>(LeftOut.data()+base); - for(ALsizei i{0};i < todo;i++) - left[i] += (S[i] + D[i]) * 0.5f; - /* Right = (S - D)/2.0 */ - ALfloat *RESTRICT right = al::assume_aligned<16>(RightOut.data()+base); - for(ALsizei 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 53e4f89e..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 ALsizei SamplesToDo); - - DEF_NEWDEL(Uhj2Encoder) -}; - -#endif /* UHJFILTER_H */ diff --git a/Alc/vector.h b/Alc/vector.h deleted file mode 100644 index 1b69d6a7..00000000 --- a/Alc/vector.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef AL_VECTOR_H -#define AL_VECTOR_H - -#include <vector> - -#include "almalloc.h" - -namespace al { - -template<typename T, size_t alignment=alignof(T)> -using vector = std::vector<T, al::allocator<T, alignment>>; - -} // namespace al - -#endif /* AL_VECTOR_H */ |