@ -0,0 +1,66 @@ | |||||
name: CI | |||||
on: [push] | |||||
jobs: | |||||
build: | |||||
name: ${{matrix.config.name}} | |||||
runs-on: ${{matrix.config.os}} | |||||
strategy: | |||||
fail-fast: false | |||||
matrix: | |||||
config: | |||||
- { | |||||
name: "Visual Studio 64-bit", | |||||
os: windows-latest, | |||||
cmake_opts: "-A x64 \ | |||||
-DALSOFT_BUILD_ROUTER=ON \ | |||||
-DALSOFT_REQUIRE_WINMM=ON \ | |||||
-DALSOFT_REQUIRE_DSOUND=ON \ | |||||
-DALSOFT_REQUIRE_WASAPI=ON", | |||||
build_type: "Release" | |||||
} | |||||
- { | |||||
name: "macOS", | |||||
os: macos-latest, | |||||
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON", | |||||
build_type: "Release" | |||||
} | |||||
- { | |||||
name: "Linux", | |||||
os: ubuntu-latest, | |||||
cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \ | |||||
-DALSOFT_REQUIRE_ALSA=ON \ | |||||
-DALSOFT_REQUIRE_OSS=ON \ | |||||
-DALSOFT_REQUIRE_PORTAUDIO=ON \ | |||||
-DALSOFT_REQUIRE_PULSEAUDIO=ON \ | |||||
-DALSOFT_REQUIRE_JACK=ON", | |||||
deps_cmdline: "sudo apt update && sudo apt-get install -qq \ | |||||
libpulse-dev \ | |||||
portaudio19-dev \ | |||||
libasound2-dev \ | |||||
libjack-dev \ | |||||
qtbase5-dev \ | |||||
libdbus-1-dev", | |||||
build_type: "Release" | |||||
} | |||||
steps: | |||||
- uses: actions/checkout@v1 | |||||
- name: Install Dependencies | |||||
shell: bash | |||||
run: | | |||||
if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then | |||||
eval ${{matrix.config.deps_cmdline}} | |||||
fi | |||||
- name: Configure | |||||
shell: bash | |||||
run: | | |||||
cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} . | |||||
- name: Build | |||||
shell: bash | |||||
run: | | |||||
cmake --build build --config ${{matrix.config.build_type}} |
@ -1,5 +1,9 @@ | |||||
build*/ | build*/ | ||||
winbuild/ | |||||
win64build/ | |||||
openal-soft.kdev4 | |||||
.kdev4/ | |||||
winbuild | |||||
win64build | |||||
## kdevelop | |||||
*.kdev4 | |||||
## qt-creator | |||||
CMakeLists.txt.user* |
@ -1,27 +1,18 @@ | |||||
#ifndef ALCONFIG_H | #ifndef ALCONFIG_H | ||||
#define ALCONFIG_H | #define ALCONFIG_H | ||||
#ifdef __cplusplus | |||||
#define NOEXCEPT noexcept | |||||
extern "C" { | |||||
#else | |||||
#define NOEXCEPT | |||||
#endif | |||||
#include <string> | |||||
void ReadALConfig(void) NOEXCEPT; | |||||
#include "aloptional.h" | |||||
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); | |||||
void ReadALConfig(); | |||||
int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret); | |||||
int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret); | |||||
int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret); | |||||
int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret); | |||||
int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret); | |||||
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def); | |||||
#ifdef __cplusplus | |||||
} // extern "C" | |||||
#endif | |||||
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 */ | #endif /* ALCONFIG_H */ |
@ -1,214 +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" | |||||
struct ALsource; | |||||
struct ALeffectslot; | |||||
struct ALcontextProps; | |||||
struct ALlistenerProps; | |||||
struct ALvoiceProps; | |||||
struct ALeffectslotProps; | |||||
struct ALvoice; | |||||
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}; | |||||
ALvoice **Voices{nullptr}; | |||||
std::atomic<ALsizei> VoiceCount{0}; | |||||
ALsizei MaxVoices{0}; | |||||
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}; | |||||
std::atomic<ALCcontext*> next{nullptr}; | |||||
ALlistener Listener{}; | |||||
ALCcontext(ALCdevice *device); | |||||
ALCcontext(const ALCcontext&) = delete; | |||||
ALCcontext& operator=(const ALCcontext&) = delete; | |||||
~ALCcontext(); | |||||
static constexpr inline const char *CurrentPrefix() noexcept { return "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->() noexcept { return mCtx; } | |||||
ALCcontext* get() noexcept { return mCtx; } | |||||
ALCcontext* release() noexcept | |||||
{ | |||||
ALCcontext *ret{mCtx}; | |||||
mCtx = nullptr; | |||||
return ret; | |||||
} | |||||
}; | |||||
ContextRef GetContextRef(void); | |||||
struct ALcontextProps { | |||||
ALfloat DopplerFactor; | |||||
ALfloat DopplerVelocity; | |||||
ALfloat SpeedOfSound; | |||||
ALboolean SourceDistanceModel; | |||||
DistanceModel mDistanceModel; | |||||
ALfloat MetersPerUnit; | |||||
std::atomic<ALcontextProps*> next; | |||||
}; | |||||
#endif /* ALCONTEXT_H */ |
@ -0,0 +1,38 @@ | |||||
#ifndef ALU_H | |||||
#define ALU_H | |||||
#include <bitset> | |||||
#include "aloptional.h" | |||||
struct ALCcontext; | |||||
struct ALCdevice; | |||||
struct EffectSlot; | |||||
enum class StereoEncoding : unsigned char; | |||||
constexpr float GainMixMax{1000.0f}; /* +60dB */ | |||||
enum CompatFlags : uint8_t { | |||||
ReverseX, | |||||
ReverseY, | |||||
ReverseZ, | |||||
Count | |||||
}; | |||||
using CompatFlagBitset = std::bitset<CompatFlags::Count>; | |||||
void aluInit(CompatFlagBitset flags); | |||||
/* aluInitRenderer | |||||
* | |||||
* Set up the appropriate panning method and mixing method given the device | |||||
* properties. | |||||
*/ | |||||
void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode); | |||||
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); | |||||
#endif |
@ -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 */ |
@ -1,58 +1,194 @@ | |||||
#include "config.h" | #include "config.h" | ||||
#include <cstdlib> | |||||
#include "base.h" | |||||
#include <thread> | |||||
#include <algorithm> | |||||
#include <array> | |||||
#include <atomic> | |||||
#include "alMain.h" | |||||
#include "alu.h" | |||||
#ifdef _WIN32 | |||||
#define WIN32_LEAN_AND_MEAN | |||||
#include <windows.h> | |||||
#include <mmreg.h> | |||||
#include "backends/base.h" | |||||
#include "albit.h" | |||||
#include "core/logging.h" | |||||
#include "aloptional.h" | |||||
#endif | |||||
#include "atomic.h" | |||||
#include "core/devformat.h" | |||||
ClockLatency GetClockLatency(ALCdevice *device) | |||||
{ | |||||
BackendBase *backend{device->Backend.get()}; | |||||
ClockLatency ret{backend->getClockLatency()}; | |||||
ret.Latency += device->FixedLatency; | |||||
return ret; | |||||
} | |||||
bool BackendBase::reset() | |||||
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } | |||||
/* BackendBase method implementations. */ | |||||
BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device} | |||||
void BackendBase::captureSamples(al::byte*, uint) | |||||
{ } | { } | ||||
BackendBase::~BackendBase() = default; | |||||
ALCboolean BackendBase::reset() | |||||
{ return ALC_FALSE; } | |||||
ALCenum BackendBase::captureSamples(void* UNUSED(buffer), ALCuint UNUSED(samples)) | |||||
{ return ALC_INVALID_DEVICE; } | |||||
ALCuint BackendBase::availableSamples() | |||||
uint BackendBase::availableSamples() | |||||
{ return 0; } | { return 0; } | ||||
ClockLatency BackendBase::getClockLatency() | ClockLatency BackendBase::getClockLatency() | ||||
{ | { | ||||
ClockLatency ret; | ClockLatency ret; | ||||
ALuint refcount; | |||||
uint refcount; | |||||
do { | do { | ||||
while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1)) | |||||
std::this_thread::yield(); | |||||
refcount = mDevice->waitForMix(); | |||||
ret.ClockTime = GetDeviceClockTime(mDevice); | ret.ClockTime = GetDeviceClockTime(mDevice); | ||||
std::atomic_thread_fence(std::memory_order_acquire); | std::atomic_thread_fence(std::memory_order_acquire); | ||||
} while(refcount != mDevice->MixCount.load(std::memory_order_relaxed)); | |||||
} while(refcount != ReadRef(mDevice->MixCount)); | |||||
/* NOTE: The device will generally have about all but one periods filled at | /* NOTE: The device will generally have about all but one periods filled at | ||||
* any given time during playback. Without a more accurate measurement from | * any given time during playback. Without a more accurate measurement from | ||||
* the output, this is an okay approximation. | * the output, this is an okay approximation. | ||||
*/ | */ | ||||
ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)}; | |||||
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, | |||||
std::chrono::seconds::zero()); | |||||
ret.Latency /= mDevice->Frequency; | ret.Latency /= mDevice->Frequency; | ||||
return ret; | return ret; | ||||
} | } | ||||
void BackendBase::setDefaultWFXChannelOrder() | |||||
{ | |||||
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); | |||||
switch(mDevice->FmtChans) | |||||
{ | |||||
case DevFmtMono: | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 0; | |||||
break; | |||||
case DevFmtStereo: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
break; | |||||
case DevFmtQuad: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[BackLeft] = 2; | |||||
mDevice->RealOut.ChannelIndex[BackRight] = 3; | |||||
break; | |||||
case DevFmtX51: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 2; | |||||
mDevice->RealOut.ChannelIndex[LFE] = 3; | |||||
mDevice->RealOut.ChannelIndex[SideLeft] = 4; | |||||
mDevice->RealOut.ChannelIndex[SideRight] = 5; | |||||
break; | |||||
case DevFmtX61: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 2; | |||||
mDevice->RealOut.ChannelIndex[LFE] = 3; | |||||
mDevice->RealOut.ChannelIndex[BackCenter] = 4; | |||||
mDevice->RealOut.ChannelIndex[SideLeft] = 5; | |||||
mDevice->RealOut.ChannelIndex[SideRight] = 6; | |||||
break; | |||||
case DevFmtX71: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 2; | |||||
mDevice->RealOut.ChannelIndex[LFE] = 3; | |||||
mDevice->RealOut.ChannelIndex[BackLeft] = 4; | |||||
mDevice->RealOut.ChannelIndex[BackRight] = 5; | |||||
mDevice->RealOut.ChannelIndex[SideLeft] = 6; | |||||
mDevice->RealOut.ChannelIndex[SideRight] = 7; | |||||
break; | |||||
case DevFmtAmbi3D: | |||||
break; | |||||
} | |||||
} | |||||
void BackendBase::setDefaultChannelOrder() | |||||
{ | |||||
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); | |||||
switch(mDevice->FmtChans) | |||||
{ | |||||
case DevFmtX51: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[SideLeft] = 2; | |||||
mDevice->RealOut.ChannelIndex[SideRight] = 3; | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 4; | |||||
mDevice->RealOut.ChannelIndex[LFE] = 5; | |||||
return; | |||||
case DevFmtX71: | |||||
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; | |||||
mDevice->RealOut.ChannelIndex[FrontRight] = 1; | |||||
mDevice->RealOut.ChannelIndex[BackLeft] = 2; | |||||
mDevice->RealOut.ChannelIndex[BackRight] = 3; | |||||
mDevice->RealOut.ChannelIndex[FrontCenter] = 4; | |||||
mDevice->RealOut.ChannelIndex[LFE] = 5; | |||||
mDevice->RealOut.ChannelIndex[SideLeft] = 6; | |||||
mDevice->RealOut.ChannelIndex[SideRight] = 7; | |||||
return; | |||||
/* Same as WFX order */ | |||||
case DevFmtMono: | |||||
case DevFmtStereo: | |||||
case DevFmtQuad: | |||||
case DevFmtX61: | |||||
case DevFmtAmbi3D: | |||||
setDefaultWFXChannelOrder(); | |||||
break; | |||||
} | |||||
} | |||||
#ifdef _WIN32 | |||||
void BackendBase::setChannelOrderFromWFXMask(uint chanmask) | |||||
{ | |||||
static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | |||||
| SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT}; | |||||
static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | |||||
| SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT}; | |||||
/* Swap a 5.1 mask using the back channels for one with the sides. */ | |||||
if(chanmask == x51rear) chanmask = x51; | |||||
auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel> | |||||
{ | |||||
switch(chanbit) | |||||
{ | |||||
case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft); | |||||
case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight); | |||||
case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter); | |||||
case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE); | |||||
case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft); | |||||
case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight); | |||||
case SPEAKER_FRONT_LEFT_OF_CENTER: break; | |||||
case SPEAKER_FRONT_RIGHT_OF_CENTER: break; | |||||
case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter); | |||||
case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft); | |||||
case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight); | |||||
case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter); | |||||
case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft); | |||||
case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter); | |||||
case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight); | |||||
case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft); | |||||
case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter); | |||||
case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight); | |||||
} | |||||
WARN("Unhandled WFX channel bit 0x%lx\n", chanbit); | |||||
return al::nullopt; | |||||
}; | |||||
const uint numchans{mDevice->channelsFromFmt()}; | |||||
uint idx{0}; | |||||
while(chanmask) | |||||
{ | |||||
const int bit{al::countr_zero(chanmask)}; | |||||
const uint mask{1u << bit}; | |||||
chanmask &= ~mask; | |||||
if(auto label = get_channel(mask)) | |||||
{ | |||||
mDevice->RealOut.ChannelIndex[*label] = idx; | |||||
if(++idx == numchans) break; | |||||
} | |||||
} | |||||
} | |||||
#endif |
@ -0,0 +1,384 @@ | |||||
#include "config.h" | |||||
#include "oboe.h" | |||||
#include <cassert> | |||||
#include <cstring> | |||||
#include <stdint.h> | |||||
#include "alnumeric.h" | |||||
#include "core/device.h" | |||||
#include "core/logging.h" | |||||
#include "oboe/Oboe.h" | |||||
namespace { | |||||
constexpr char device_name[] = "Oboe Default"; | |||||
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { | |||||
OboePlayback(DeviceBase *device) : BackendBase{device} { } | |||||
oboe::ManagedStream mStream; | |||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, | |||||
int32_t numFrames) override; | |||||
void open(const char *name) override; | |||||
bool reset() override; | |||||
void start() override; | |||||
void stop() override; | |||||
}; | |||||
oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, | |||||
int32_t numFrames) | |||||
{ | |||||
assert(numFrames > 0); | |||||
const int32_t numChannels{oboeStream->getChannelCount()}; | |||||
mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames), | |||||
static_cast<uint32_t>(numChannels)); | |||||
return oboe::DataCallbackResult::Continue; | |||||
} | |||||
void OboePlayback::open(const char *name) | |||||
{ | |||||
if(!name) | |||||
name = device_name; | |||||
else if(std::strcmp(name, device_name) != 0) | |||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", | |||||
name}; | |||||
/* Open a basic output stream, just to ensure it can work. */ | |||||
oboe::ManagedStream stream; | |||||
oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) | |||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency) | |||||
->openManagedStream(stream)}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", | |||||
oboe::convertToText(result)}; | |||||
mDevice->DeviceName = name; | |||||
} | |||||
bool OboePlayback::reset() | |||||
{ | |||||
oboe::AudioStreamBuilder builder; | |||||
builder.setDirection(oboe::Direction::Output); | |||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); | |||||
/* Don't let Oboe convert. We should be able to handle anything it gives | |||||
* back. | |||||
*/ | |||||
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); | |||||
builder.setChannelConversionAllowed(false); | |||||
builder.setFormatConversionAllowed(false); | |||||
builder.setCallback(this); | |||||
if(mDevice->Flags.test(FrequencyRequest)) | |||||
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency)); | |||||
if(mDevice->Flags.test(ChannelsRequest)) | |||||
{ | |||||
/* Only use mono or stereo at user request. There's no telling what | |||||
* other counts may be inferred as. | |||||
*/ | |||||
builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono | |||||
: (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo | |||||
: oboe::ChannelCount::Unspecified); | |||||
} | |||||
if(mDevice->Flags.test(SampleTypeRequest)) | |||||
{ | |||||
oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; | |||||
switch(mDevice->FmtType) | |||||
{ | |||||
case DevFmtByte: | |||||
case DevFmtUByte: | |||||
case DevFmtShort: | |||||
case DevFmtUShort: | |||||
format = oboe::AudioFormat::I16; | |||||
break; | |||||
case DevFmtInt: | |||||
case DevFmtUInt: | |||||
case DevFmtFloat: | |||||
format = oboe::AudioFormat::Float; | |||||
break; | |||||
} | |||||
builder.setFormat(format); | |||||
} | |||||
oboe::Result result{builder.openManagedStream(mStream)}; | |||||
/* If the format failed, try asking for the defaults. */ | |||||
while(result == oboe::Result::ErrorInvalidFormat) | |||||
{ | |||||
if(builder.getFormat() != oboe::AudioFormat::Unspecified) | |||||
builder.setFormat(oboe::AudioFormat::Unspecified); | |||||
else if(builder.getSampleRate() != oboe::kUnspecified) | |||||
builder.setSampleRate(oboe::kUnspecified); | |||||
else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) | |||||
builder.setChannelCount(oboe::ChannelCount::Unspecified); | |||||
else | |||||
break; | |||||
result = builder.openManagedStream(mStream); | |||||
} | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", | |||||
oboe::convertToText(result)}; | |||||
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize), | |||||
mStream->getBufferCapacityInFrames())); | |||||
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); | |||||
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt()) | |||||
{ | |||||
if(mStream->getChannelCount() >= 2) | |||||
mDevice->FmtChans = DevFmtStereo; | |||||
else if(mStream->getChannelCount() == 1) | |||||
mDevice->FmtChans = DevFmtMono; | |||||
else | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"Got unhandled channel count: %d", mStream->getChannelCount()}; | |||||
} | |||||
setDefaultWFXChannelOrder(); | |||||
switch(mStream->getFormat()) | |||||
{ | |||||
case oboe::AudioFormat::I16: | |||||
mDevice->FmtType = DevFmtShort; | |||||
break; | |||||
case oboe::AudioFormat::Float: | |||||
mDevice->FmtType = DevFmtFloat; | |||||
break; | |||||
case oboe::AudioFormat::Unspecified: | |||||
case oboe::AudioFormat::Invalid: | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; | |||||
} | |||||
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate()); | |||||
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 | |||||
* indicating variable updates, but OpenAL should have a reasonable minimum update size set. | |||||
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum | |||||
* update size. | |||||
*/ | |||||
mDevice->UpdateSize = maxu(mDevice->Frequency / 100, | |||||
static_cast<uint32_t>(mStream->getFramesPerBurst())); | |||||
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, | |||||
static_cast<uint32_t>(mStream->getBufferSizeInFrames())); | |||||
return true; | |||||
} | |||||
void OboePlayback::start() | |||||
{ | |||||
const oboe::Result result{mStream->start()}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", | |||||
oboe::convertToText(result)}; | |||||
} | |||||
void OboePlayback::stop() | |||||
{ | |||||
oboe::Result result{mStream->stop()}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", | |||||
oboe::convertToText(result)}; | |||||
} | |||||
struct OboeCapture final : public BackendBase { | |||||
OboeCapture(DeviceBase *device) : BackendBase{device} { } | |||||
oboe::ManagedStream mStream; | |||||
std::vector<al::byte> mSamples; | |||||
uint mLastAvail{0u}; | |||||
void open(const char *name) override; | |||||
void start() override; | |||||
void stop() override; | |||||
void captureSamples(al::byte *buffer, uint samples) override; | |||||
uint availableSamples() override; | |||||
}; | |||||
void OboeCapture::open(const char *name) | |||||
{ | |||||
if(!name) | |||||
name = device_name; | |||||
else if(std::strcmp(name, device_name) != 0) | |||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", | |||||
name}; | |||||
oboe::AudioStreamBuilder builder; | |||||
builder.setDirection(oboe::Direction::Input) | |||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency) | |||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) | |||||
->setChannelConversionAllowed(true) | |||||
->setFormatConversionAllowed(true) | |||||
->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize)) | |||||
->setSampleRate(static_cast<int32_t>(mDevice->Frequency)); | |||||
/* Only use mono or stereo at user request. There's no telling what | |||||
* other counts may be inferred as. | |||||
*/ | |||||
switch(mDevice->FmtChans) | |||||
{ | |||||
case DevFmtMono: | |||||
builder.setChannelCount(oboe::ChannelCount::Mono); | |||||
break; | |||||
case DevFmtStereo: | |||||
builder.setChannelCount(oboe::ChannelCount::Stereo); | |||||
break; | |||||
case DevFmtQuad: | |||||
case DevFmtX51: | |||||
case DevFmtX61: | |||||
case DevFmtX71: | |||||
case DevFmtAmbi3D: | |||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", | |||||
DevFmtChannelsString(mDevice->FmtChans)}; | |||||
} | |||||
/* FIXME: This really should support UByte, but Oboe doesn't. We'll need to | |||||
* use a temp buffer and convert. | |||||
*/ | |||||
switch(mDevice->FmtType) | |||||
{ | |||||
case DevFmtShort: | |||||
builder.setFormat(oboe::AudioFormat::I16); | |||||
break; | |||||
case DevFmtFloat: | |||||
builder.setFormat(oboe::AudioFormat::Float); | |||||
break; | |||||
case DevFmtByte: | |||||
case DevFmtUByte: | |||||
case DevFmtUShort: | |||||
case DevFmtInt: | |||||
case DevFmtUInt: | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; | |||||
} | |||||
oboe::Result result{builder.openManagedStream(mStream)}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", | |||||
oboe::convertToText(result)}; | |||||
if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames()) | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"Buffer size too large (%u > %d)", mDevice->BufferSize, | |||||
mStream->getBufferCapacityInFrames()}; | |||||
auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize)); | |||||
if(!buffer_result) | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())}; | |||||
else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize)) | |||||
throw al::backend_exception{al::backend_error::DeviceError, | |||||
"Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize, | |||||
buffer_result.value()}; | |||||
mDevice->BufferSize = static_cast<uint>(buffer_result.value()); | |||||
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); | |||||
mDevice->DeviceName = name; | |||||
} | |||||
void OboeCapture::start() | |||||
{ | |||||
const oboe::Result result{mStream->start()}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", | |||||
oboe::convertToText(result)}; | |||||
} | |||||
void OboeCapture::stop() | |||||
{ | |||||
/* Capture any unread samples before stopping. Oboe drops whatever's left | |||||
* in the stream. | |||||
*/ | |||||
if(auto availres = mStream->getAvailableFrames()) | |||||
{ | |||||
const auto avail = std::max(static_cast<uint>(availres.value()), mLastAvail); | |||||
const size_t frame_size{static_cast<uint32_t>(mStream->getBytesPerFrame())}; | |||||
const size_t pos{mSamples.size()}; | |||||
mSamples.resize(pos + avail*frame_size); | |||||
auto result = mStream->read(&mSamples[pos], availres.value(), 0); | |||||
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u}; | |||||
if(got < avail) | |||||
std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{}); | |||||
mLastAvail = 0; | |||||
} | |||||
const oboe::Result result{mStream->stop()}; | |||||
if(result != oboe::Result::OK) | |||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", | |||||
oboe::convertToText(result)}; | |||||
} | |||||
uint OboeCapture::availableSamples() | |||||
{ | |||||
/* Keep track of the max available frame count, to ensure it doesn't go | |||||
* backwards. | |||||
*/ | |||||
if(auto result = mStream->getAvailableFrames()) | |||||
mLastAvail = std::max(static_cast<uint>(result.value()), mLastAvail); | |||||
const auto frame_size = static_cast<uint32_t>(mStream->getBytesPerFrame()); | |||||
return static_cast<uint>(mSamples.size()/frame_size) + mLastAvail; | |||||
} | |||||
void OboeCapture::captureSamples(al::byte *buffer, uint samples) | |||||
{ | |||||
const auto frame_size = static_cast<uint>(mStream->getBytesPerFrame()); | |||||
if(const size_t storelen{mSamples.size()}) | |||||
{ | |||||
const auto instore = static_cast<uint>(storelen / frame_size); | |||||
const uint tocopy{std::min(samples, instore) * frame_size}; | |||||
std::copy_n(mSamples.begin(), tocopy, buffer); | |||||
mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy); | |||||
buffer += tocopy; | |||||
samples -= tocopy/frame_size; | |||||
if(!samples) return; | |||||
} | |||||
auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0); | |||||
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u}; | |||||
if(got < samples) | |||||
std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{}); | |||||
mLastAvail = std::max(mLastAvail, samples) - samples; | |||||
} | |||||
} // namespace | |||||
bool OboeBackendFactory::init() { return true; } | |||||
bool OboeBackendFactory::querySupport(BackendType type) | |||||
{ return type == BackendType::Playback || type == BackendType::Capture; } | |||||
std::string OboeBackendFactory::probe(BackendType type) | |||||
{ | |||||
switch(type) | |||||
{ | |||||
case BackendType::Playback: | |||||
case BackendType::Capture: | |||||
/* Includes null char. */ | |||||
return std::string{device_name, sizeof(device_name)}; | |||||
} | |||||
return std::string{}; | |||||
} | |||||
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) | |||||
{ | |||||
if(type == BackendType::Playback) | |||||
return BackendPtr{new OboePlayback{device}}; | |||||
if(type == BackendType::Capture) | |||||
return BackendPtr{new OboeCapture{device}}; | |||||
return BackendPtr{}; | |||||
} | |||||
BackendFactory &OboeBackendFactory::getFactory() | |||||
{ | |||||
static OboeBackendFactory factory{}; | |||||
return factory; | |||||
} |
@ -0,0 +1,19 @@ | |||||
#ifndef BACKENDS_OBOE_H | |||||
#define BACKENDS_OBOE_H | |||||
#include "base.h" | |||||
struct OboeBackendFactory final : public BackendFactory { | |||||
public: | |||||
bool init() override; | |||||
bool querySupport(BackendType type) override; | |||||
std::string probe(BackendType type) override; | |||||
BackendPtr createBackend(DeviceBase *device, BackendType type) override; | |||||
static BackendFactory &getFactory(); | |||||
}; | |||||
#endif /* BACKENDS_OBOE_H */ |
@ -0,0 +1,23 @@ | |||||
#ifndef BACKENDS_PIPEWIRE_H | |||||
#define BACKENDS_PIPEWIRE_H | |||||
#include <string> | |||||
#include "base.h" | |||||
struct DeviceBase; | |||||
struct PipeWireBackendFactory final : public BackendFactory { | |||||
public: | |||||
bool init() override; | |||||
bool querySupport(BackendType type) override; | |||||
std::string probe(BackendType type) override; | |||||
BackendPtr createBackend(DeviceBase *device, BackendType type) override; | |||||
static BackendFactory &getFactory(); | |||||
}; | |||||
#endif /* BACKENDS_PIPEWIRE_H */ |
@ -1,955 +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 "alMain.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; | |||||
static constexpr inline const char *CurrentPrefix() noexcept { return "PlaybackWrapper::"; } | |||||
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; | |||||
static constexpr inline const char *CurrentPrefix() noexcept { return "CaptureWrapper::"; } | |||||
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; | |||||
} |
@ -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 */ |
@ -1,201 +0,0 @@ | |||||
#include "config.h" | |||||
#include <cmath> | |||||
#include <array> | |||||
#include <vector> | |||||
#include <numeric> | |||||
#include <algorithm> | |||||
#include <functional> | |||||
#include "bformatdec.h" | |||||
#include "ambdec.h" | |||||
#include "filters/splitter.h" | |||||
#include "alu.h" | |||||
#include "threads.h" | |||||
#include "almalloc.h" | |||||
namespace { | |||||
using namespace std::placeholders; | |||||
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 ALsizei 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 ALsizei 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(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels, const ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo) | |||||
{ | |||||
ASSUME(OutChannels > 0); | |||||
ASSUME(mNumChannels > 0); | |||||
if(mDualBand) | |||||
{ | |||||
for(ALsizei i{0};i < mNumChannels;i++) | |||||
mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i], | |||||
SamplesToDo); | |||||
for(ALsizei chan{0};chan < OutChannels;chan++) | |||||
{ | |||||
if(UNLIKELY(!(mEnabled&(1<<chan)))) | |||||
continue; | |||||
MixRowSamples(OutBuffer[chan], mMatrix.Dual[chan][sHFBand], | |||||
&reinterpret_cast<ALfloat(&)[BUFFERSIZE]>(mSamplesHF[0]), | |||||
mNumChannels, 0, SamplesToDo); | |||||
MixRowSamples(OutBuffer[chan], mMatrix.Dual[chan][sLFBand], | |||||
&reinterpret_cast<ALfloat(&)[BUFFERSIZE]>(mSamplesLF[0]), | |||||
mNumChannels, 0, SamplesToDo); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for(ALsizei chan{0};chan < OutChannels;chan++) | |||||
{ | |||||
if(UNLIKELY(!(mEnabled&(1<<chan)))) | |||||
continue; | |||||
MixRowSamples(OutBuffer[chan], mMatrix.Single[chan], InSamples, | |||||
mNumChannels, 0, SamplesToDo); | |||||
} | |||||
} | |||||
} | |||||
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; | |||||
} |
@ -1,56 +0,0 @@ | |||||
#ifndef BFORMATDEC_H | |||||
#define BFORMATDEC_H | |||||
#include "alMain.h" | |||||
#include "filters/splitter.h" | |||||
#include "ambidefs.h" | |||||
#include "almalloc.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<std::array<ALfloat,BUFFERSIZE>, 16> mSamples; | |||||
/* These two alias into Samples */ | |||||
std::array<ALfloat,BUFFERSIZE> *mSamplesHF{nullptr}; | |||||
std::array<ALfloat,BUFFERSIZE> *mSamplesLF{nullptr}; | |||||
ALsizei mNumChannels{0}; | |||||
bool mDualBand{false}; | |||||
public: | |||||
BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALsizei inchans, | |||||
const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]); | |||||
BFormatDec(const ALsizei 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(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels, | |||||
const ALfloat (*InSamples)[BUFFERSIZE], 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 */ |
@ -1,236 +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 | |||||
{ | |||||
if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr()) | |||||
{ | |||||
// Read in the next chunk of data, and set the pointers on success | |||||
DWORD got = 0; | |||||
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()); | |||||
} | |||||
pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override | |||||
{ | |||||
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; | |||||
} | |||||
pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override | |||||
{ | |||||
// 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; | |||||
} | |||||
public: | |||||
bool 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 open(const char *filename, std::ios_base::openmode mode) | |||||
{ | |||||
std::wstring wname{utf8_to_wstr(filename)}; | |||||
return open(wname.c_str(), mode); | |||||
} | |||||
bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } | |||||
filebuf() = default; | |||||
~filebuf() override | |||||
{ | |||||
if(mFile != INVALID_HANDLE_VALUE) | |||||
CloseHandle(mFile); | |||||
mFile = INVALID_HANDLE_VALUE; | |||||
} | |||||
}; | |||||
// Inherit from std::istream to use our custom streambuf | |||||
class ifstream final : public std::istream { | |||||
filebuf mStreamBuf; | |||||
public: | |||||
ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in) | |||||
: ifstream(filename.c_str(), mode) { } | |||||
ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in) | |||||
: 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(const std::string &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) | |||||
: 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); | |||||
} | |||||
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 */ |
@ -0,0 +1,504 @@ | |||||
#ifndef ALC_CONTEXT_H | |||||
#define ALC_CONTEXT_H | |||||
#include <atomic> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <stdint.h> | |||||
#include <utility> | |||||
#include "AL/al.h" | |||||
#include "AL/alc.h" | |||||
#include "AL/alext.h" | |||||
#include "al/listener.h" | |||||
#include "almalloc.h" | |||||
#include "alnumeric.h" | |||||
#include "atomic.h" | |||||
#include "core/context.h" | |||||
#include "intrusive_ptr.h" | |||||
#include "vector.h" | |||||
#ifdef ALSOFT_EAX | |||||
#include "al/eax_eax_call.h" | |||||
#include "al/eax_fx_slot_index.h" | |||||
#include "al/eax_fx_slots.h" | |||||
#include "al/eax_utils.h" | |||||
using EaxContextSharedDirtyFlagsValue = std::uint_least8_t; | |||||
struct EaxContextSharedDirtyFlags | |||||
{ | |||||
using EaxIsBitFieldStruct = bool; | |||||
EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1; | |||||
}; // EaxContextSharedDirtyFlags | |||||
using ContextDirtyFlagsValue = std::uint_least8_t; | |||||
struct ContextDirtyFlags | |||||
{ | |||||
using EaxIsBitFieldStruct = bool; | |||||
ContextDirtyFlagsValue guidPrimaryFXSlotID : 1; | |||||
ContextDirtyFlagsValue flDistanceFactor : 1; | |||||
ContextDirtyFlagsValue flAirAbsorptionHF : 1; | |||||
ContextDirtyFlagsValue flHFReference : 1; | |||||
ContextDirtyFlagsValue flMacroFXFactor : 1; | |||||
}; // ContextDirtyFlags | |||||
struct EaxAlIsExtensionPresentResult | |||||
{ | |||||
ALboolean is_present; | |||||
bool is_return; | |||||
}; // EaxAlIsExtensionPresentResult | |||||
#endif // ALSOFT_EAX | |||||
struct ALeffect; | |||||
struct ALeffectslot; | |||||
struct ALsource; | |||||
using uint = unsigned int; | |||||
struct SourceSubList { | |||||
uint64_t FreeMask{~0_u64}; | |||||
ALsource *Sources{nullptr}; /* 64 */ | |||||
SourceSubList() noexcept = default; | |||||
SourceSubList(const SourceSubList&) = delete; | |||||
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} | |||||
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } | |||||
~SourceSubList(); | |||||
SourceSubList& operator=(const SourceSubList&) = delete; | |||||
SourceSubList& operator=(SourceSubList&& rhs) noexcept | |||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } | |||||
}; | |||||
struct EffectSlotSubList { | |||||
uint64_t FreeMask{~0_u64}; | |||||
ALeffectslot *EffectSlots{nullptr}; /* 64 */ | |||||
EffectSlotSubList() noexcept = default; | |||||
EffectSlotSubList(const EffectSlotSubList&) = delete; | |||||
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept | |||||
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} | |||||
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } | |||||
~EffectSlotSubList(); | |||||
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; | |||||
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept | |||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } | |||||
}; | |||||
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase { | |||||
const al::intrusive_ptr<ALCdevice> mALDevice; | |||||
/* Wet buffers used by effect slots. */ | |||||
al::vector<WetBufferPtr> mWetBuffers; | |||||
bool mPropsDirty{true}; | |||||
bool mDeferUpdates{false}; | |||||
std::mutex mPropLock; | |||||
std::atomic<ALenum> mLastError{AL_NO_ERROR}; | |||||
DistanceModel mDistanceModel{DistanceModel::Default}; | |||||
bool mSourceDistanceModel{false}; | |||||
float mDopplerFactor{1.0f}; | |||||
float mDopplerVelocity{1.0f}; | |||||
float mSpeedOfSound{SpeedOfSoundMetersPerSec}; | |||||
float mAirAbsorptionGainHF{AirAbsorbGainHF}; | |||||
std::mutex mEventCbLock; | |||||
ALEVENTPROCSOFT mEventCb{}; | |||||
void *mEventParam{nullptr}; | |||||
ALlistener mListener{}; | |||||
al::vector<SourceSubList> mSourceList; | |||||
ALuint mNumSources{0}; | |||||
std::mutex mSourceLock; | |||||
al::vector<EffectSlotSubList> mEffectSlotList; | |||||
ALuint mNumEffectSlots{0u}; | |||||
std::mutex mEffectSlotLock; | |||||
/* Default effect slot */ | |||||
std::unique_ptr<ALeffectslot> mDefaultSlot; | |||||
const char *mExtensionList{nullptr}; | |||||
ALCcontext(al::intrusive_ptr<ALCdevice> device); | |||||
ALCcontext(const ALCcontext&) = delete; | |||||
ALCcontext& operator=(const ALCcontext&) = delete; | |||||
~ALCcontext(); | |||||
void init(); | |||||
/** | |||||
* Removes the context from its device and removes it from being current on | |||||
* the running thread or globally. Returns true if other contexts still | |||||
* exist on the device. | |||||
*/ | |||||
bool deinit(); | |||||
/** | |||||
* Defers/suspends updates for the given context's listener and sources. | |||||
* This does *NOT* stop mixing, but rather prevents certain property | |||||
* changes from taking effect. mPropLock must be held when called. | |||||
*/ | |||||
void deferUpdates() noexcept { mDeferUpdates = true; } | |||||
/** | |||||
* Resumes update processing after being deferred. mPropLock must be held | |||||
* when called. | |||||
*/ | |||||
void processUpdates() | |||||
{ | |||||
if(std::exchange(mDeferUpdates, false)) | |||||
applyAllUpdates(); | |||||
} | |||||
/** | |||||
* Applies all pending updates for the context, listener, effect slots, and | |||||
* sources. | |||||
*/ | |||||
void applyAllUpdates(); | |||||
#ifdef __USE_MINGW_ANSI_STDIO | |||||
[[gnu::format(gnu_printf, 3, 4)]] | |||||
#else | |||||
[[gnu::format(printf, 3, 4)]] | |||||
#endif | |||||
void setError(ALenum errorCode, const char *msg, ...); | |||||
/* Process-wide current context */ | |||||
static std::atomic<ALCcontext*> sGlobalContext; | |||||
private: | |||||
/* Thread-local current context. */ | |||||
static thread_local ALCcontext *sLocalContext; | |||||
/* Thread-local context handling. This handles attempting to release the | |||||
* context which may have been left current when the thread is destroyed. | |||||
*/ | |||||
class ThreadCtx { | |||||
public: | |||||
~ThreadCtx(); | |||||
void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } | |||||
}; | |||||
static thread_local ThreadCtx sThreadContext; | |||||
public: | |||||
/* HACK: MinGW generates bad code when accessing an extern thread_local | |||||
* object. Add a wrapper function for it that only accesses it where it's | |||||
* defined. | |||||
*/ | |||||
#ifdef __MINGW32__ | |||||
static ALCcontext *getThreadContext() noexcept; | |||||
static void setThreadContext(ALCcontext *context) noexcept; | |||||
#else | |||||
static ALCcontext *getThreadContext() noexcept { return sLocalContext; } | |||||
static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } | |||||
#endif | |||||
/* Default effect that applies to sources that don't have an effect on send 0. */ | |||||
static ALeffect sDefaultEffect; | |||||
DEF_NEWDEL(ALCcontext) | |||||
#ifdef ALSOFT_EAX | |||||
public: | |||||
bool has_eax() const noexcept { return eax_is_initialized_; } | |||||
bool eax_is_capable() const noexcept; | |||||
void eax_uninitialize() noexcept; | |||||
ALenum eax_eax_set( | |||||
const GUID* property_set_id, | |||||
ALuint property_id, | |||||
ALuint property_source_id, | |||||
ALvoid* property_value, | |||||
ALuint property_value_size); | |||||
ALenum eax_eax_get( | |||||
const GUID* property_set_id, | |||||
ALuint property_id, | |||||
ALuint property_source_id, | |||||
ALvoid* property_value, | |||||
ALuint property_value_size); | |||||
void eax_update_filters(); | |||||
void eax_commit_and_update_sources(); | |||||
void eax_set_last_error() noexcept; | |||||
EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept | |||||
{ return eax_previous_primary_fx_slot_index_; } | |||||
EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept | |||||
{ return eax_primary_fx_slot_index_; } | |||||
const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const | |||||
{ return eax_fx_slots_.get(fx_slot_index); } | |||||
ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) | |||||
{ return eax_fx_slots_.get(fx_slot_index); } | |||||
void eax_commit_fx_slots() | |||||
{ eax_fx_slots_.commit(); } | |||||
private: | |||||
struct Eax | |||||
{ | |||||
EAX50CONTEXTPROPERTIES context{}; | |||||
}; // Eax | |||||
bool eax_is_initialized_{}; | |||||
bool eax_is_tried_{}; | |||||
bool eax_are_legacy_fx_slots_unlocked_{}; | |||||
long eax_last_error_{}; | |||||
unsigned long eax_speaker_config_{}; | |||||
EaxFxSlotIndex eax_previous_primary_fx_slot_index_{}; | |||||
EaxFxSlotIndex eax_primary_fx_slot_index_{}; | |||||
EaxFxSlots eax_fx_slots_{}; | |||||
EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{}; | |||||
Eax eax_{}; | |||||
Eax eax_d_{}; | |||||
EAXSESSIONPROPERTIES eax_session_{}; | |||||
ContextDirtyFlags eax_context_dirty_flags_{}; | |||||
std::string eax_extension_list_{}; | |||||
[[noreturn]] | |||||
static void eax_fail( | |||||
const char* message); | |||||
void eax_initialize_extensions(); | |||||
void eax_initialize(); | |||||
bool eax_has_no_default_effect_slot() const noexcept; | |||||
void eax_ensure_no_default_effect_slot() const; | |||||
bool eax_has_enough_aux_sends() const noexcept; | |||||
void eax_ensure_enough_aux_sends() const; | |||||
void eax_ensure_compatibility(); | |||||
unsigned long eax_detect_speaker_configuration() const; | |||||
void eax_update_speaker_configuration(); | |||||
void eax_set_last_error_defaults() noexcept; | |||||
void eax_set_session_defaults() noexcept; | |||||
void eax_set_context_defaults() noexcept; | |||||
void eax_set_defaults() noexcept; | |||||
void eax_initialize_sources(); | |||||
void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept; | |||||
void eax_dispatch_fx_slot( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_dispatch_source( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_primary_fx_slot_id( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_distance_factor( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_air_absorption_hf( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_hf_reference( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_last_error( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_speaker_config( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_session( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_macro_fx_factor( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get_context_all( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_get( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_set_primary_fx_slot_id(); | |||||
void eax_set_distance_factor(); | |||||
void eax_set_air_absorbtion_hf(); | |||||
void eax_set_hf_reference(); | |||||
void eax_set_macro_fx_factor(); | |||||
void eax_set_context(); | |||||
void eax_initialize_fx_slots(); | |||||
void eax_update_sources(); | |||||
void eax_validate_primary_fx_slot_id( | |||||
const GUID& primary_fx_slot_id); | |||||
void eax_validate_distance_factor( | |||||
float distance_factor); | |||||
void eax_validate_air_absorption_hf( | |||||
float air_absorption_hf); | |||||
void eax_validate_hf_reference( | |||||
float hf_reference); | |||||
void eax_validate_speaker_config( | |||||
unsigned long speaker_config); | |||||
void eax_validate_session_eax_version( | |||||
unsigned long eax_version); | |||||
void eax_validate_session_max_active_sends( | |||||
unsigned long max_active_sends); | |||||
void eax_validate_session( | |||||
const EAXSESSIONPROPERTIES& eax_session); | |||||
void eax_validate_macro_fx_factor( | |||||
float macro_fx_factor); | |||||
void eax_validate_context_all( | |||||
const EAX40CONTEXTPROPERTIES& context_all); | |||||
void eax_validate_context_all( | |||||
const EAX50CONTEXTPROPERTIES& context_all); | |||||
void eax_defer_primary_fx_slot_id( | |||||
const GUID& primary_fx_slot_id); | |||||
void eax_defer_distance_factor( | |||||
float distance_factor); | |||||
void eax_defer_air_absorption_hf( | |||||
float air_absorption_hf); | |||||
void eax_defer_hf_reference( | |||||
float hf_reference); | |||||
void eax_defer_macro_fx_factor( | |||||
float macro_fx_factor); | |||||
void eax_defer_context_all( | |||||
const EAX40CONTEXTPROPERTIES& context_all); | |||||
void eax_defer_context_all( | |||||
const EAX50CONTEXTPROPERTIES& context_all); | |||||
void eax_defer_context_all( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_defer_primary_fx_slot_id( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_defer_distance_factor( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_defer_air_absorption_hf( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_defer_hf_reference( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_set_session( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_defer_macro_fx_factor( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_set( | |||||
const EaxEaxCall& eax_call); | |||||
void eax_apply_deferred(); | |||||
#endif // ALSOFT_EAX | |||||
}; | |||||
#define SETERR_RETURN(ctx, err, retval, ...) do { \ | |||||
(ctx)->setError((err), __VA_ARGS__); \ | |||||
return retval; \ | |||||
} while(0) | |||||
using ContextRef = al::intrusive_ptr<ALCcontext>; | |||||
ContextRef GetContextRef(void); | |||||
void UpdateContextProps(ALCcontext *context); | |||||
extern bool TrapALError; | |||||
#ifdef ALSOFT_EAX | |||||
ALenum AL_APIENTRY EAXSet( | |||||
const GUID* property_set_id, | |||||
ALuint property_id, | |||||
ALuint property_source_id, | |||||
ALvoid* property_value, | |||||
ALuint property_value_size) noexcept; | |||||
ALenum AL_APIENTRY EAXGet( | |||||
const GUID* property_set_id, | |||||
ALuint property_id, | |||||
ALuint property_source_id, | |||||
ALvoid* property_value, | |||||
ALuint property_value_size) noexcept; | |||||
#endif // ALSOFT_EAX | |||||
#endif /* ALC_CONTEXT_H */ |
@ -1,369 +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); | |||||
template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val) | |||||
{ return val * (1.0f/128.0f); } | |||||
template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val) | |||||
{ return val * (1.0f/32768.0f); } | |||||
template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val) | |||||
{ return val * (1.0f/2147483648.0f); } | |||||
template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val) | |||||
{ return val; } | |||||
template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val) | |||||
{ return LoadSample<DevFmtByte>(val - 128); } | |||||
template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) | |||||
{ return LoadSample<DevFmtByte>(val - 32768); } | |||||
template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) | |||||
{ return LoadSample<DevFmtByte>(val - 2147483648u); } | |||||
template<DevFmtType T> | |||||
inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, size_t srcstep, ALsizei samples) | |||||
{ | |||||
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, size_t srcstep, DevFmtType srctype, ALsizei samples) | |||||
{ | |||||
#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); | |||||
template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val) | |||||
{ return val; } | |||||
template<> inline ALint StoreSample<DevFmtInt>(ALfloat val) | |||||
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } | |||||
template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val) | |||||
{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); } | |||||
template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) | |||||
{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); } | |||||
/* Define unsigned output variations. */ | |||||
template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) | |||||
{ return StoreSample<DevFmtInt>(val) + 2147483648u; } | |||||
template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) | |||||
{ return StoreSample<DevFmtShort>(val) + 32768; } | |||||
template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) | |||||
{ return StoreSample<DevFmtByte>(val) + 128; } | |||||
template<DevFmtType T> | |||||
inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, size_t dststep, | |||||
ALsizei samples) | |||||
{ | |||||
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, size_t dststep, DevFmtType dsttype, ALsizei samples) | |||||
{ | |||||
#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, ALsizei frames) | |||||
{ | |||||
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, ALsizei frames) | |||||
{ | |||||
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 ALbyte*>(*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 ALbyte *SrcSamples = SamplesIn + mSrcTypeSize*chan; | |||||
ALbyte *DstSamples = static_cast<ALbyte*>(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<ALbyte*>(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 ChannelConverterPtr{new ChannelConverter{srcType, srcChans, dstChans}}; | |||||
} | |||||
void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const | |||||
{ | |||||
if(mSrcChans == mDstChans) | |||||
{ | |||||
LoadSamples(dst, src, 1u, mSrcType, frames*ChannelsFromDevFmt(mSrcChans, 0)); | |||||
return; | |||||
} | |||||
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 | |||||
} | |||||
} | |||||
} |
@ -1,70 +0,0 @@ | |||||
#ifndef CONVERTER_H | |||||
#define CONVERTER_H | |||||
#include <memory> | |||||
#include "alMain.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 */ |
@ -1,23 +0,0 @@ | |||||
#ifndef CPU_CAPS_H | |||||
#define CPU_CAPS_H | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
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); | |||||
#ifdef __cplusplus | |||||
} // extern "C" | |||||
#endif | |||||
#endif /* CPU_CAPS_H */ |
@ -0,0 +1,90 @@ | |||||
#include "config.h" | |||||
#include "device.h" | |||||
#include <numeric> | |||||
#include <stddef.h> | |||||
#include "albit.h" | |||||
#include "alconfig.h" | |||||
#include "backends/base.h" | |||||
#include "core/bformatdec.h" | |||||
#include "core/bs2b.h" | |||||
#include "core/front_stablizer.h" | |||||
#include "core/hrtf.h" | |||||
#include "core/logging.h" | |||||
#include "core/mastering.h" | |||||
#include "core/uhjfilter.h" | |||||
namespace { | |||||
using voidp = void*; | |||||
} // namespace | |||||
ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} | |||||
{ } | |||||
ALCdevice::~ALCdevice() | |||||
{ | |||||
TRACE("Freeing device %p\n", voidp{this}); | |||||
Backend = nullptr; | |||||
size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, | |||||
[](size_t cur, const BufferSubList &sublist) noexcept -> size_t | |||||
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })}; | |||||
if(count > 0) | |||||
WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); | |||||
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, | |||||
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t | |||||
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); }); | |||||
if(count > 0) | |||||
WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); | |||||
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, | |||||
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t | |||||
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); }); | |||||
if(count > 0) | |||||
WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); | |||||
} | |||||
void ALCdevice::enumerateHrtfs() | |||||
{ | |||||
mHrtfList = EnumerateHrtf(configValue<std::string>(nullptr, "hrtf-paths")); | |||||
if(auto defhrtfopt = configValue<std::string>(nullptr, "default-hrtf")) | |||||
{ | |||||
auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); | |||||
if(iter == mHrtfList.end()) | |||||
WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); | |||||
else if(iter != mHrtfList.begin()) | |||||
std::rotate(mHrtfList.begin(), iter, iter+1); | |||||
} | |||||
} | |||||
auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 | |||||
{ | |||||
if(mContexts.load(std::memory_order_relaxed)->empty()) | |||||
return OutputMode1::Any; | |||||
switch(FmtChans) | |||||
{ | |||||
case DevFmtMono: return OutputMode1::Mono; | |||||
case DevFmtStereo: | |||||
if(mHrtf) | |||||
return OutputMode1::Hrtf; | |||||
else if(mUhjEncoder) | |||||
return OutputMode1::Uhj2; | |||||
return OutputMode1::StereoBasic; | |||||
case DevFmtQuad: return OutputMode1::Quad; | |||||
case DevFmtX51: return OutputMode1::X51; | |||||
case DevFmtX61: return OutputMode1::X61; | |||||
case DevFmtX71: return OutputMode1::X71; | |||||
case DevFmtAmbi3D: break; | |||||
} | |||||
return OutputMode1::Any; | |||||
} |
@ -0,0 +1,165 @@ | |||||
#ifndef ALC_DEVICE_H | |||||
#define ALC_DEVICE_H | |||||
#include <atomic> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <stdint.h> | |||||
#include <string> | |||||
#include <utility> | |||||
#include "AL/alc.h" | |||||
#include "AL/alext.h" | |||||
#include "alconfig.h" | |||||
#include "almalloc.h" | |||||
#include "alnumeric.h" | |||||
#include "core/device.h" | |||||
#include "inprogext.h" | |||||
#include "intrusive_ptr.h" | |||||
#include "vector.h" | |||||
#ifdef ALSOFT_EAX | |||||
#include "al/eax_x_ram.h" | |||||
#endif // ALSOFT_EAX | |||||
struct ALbuffer; | |||||
struct ALeffect; | |||||
struct ALfilter; | |||||
struct BackendBase; | |||||
using uint = unsigned int; | |||||
struct BufferSubList { | |||||
uint64_t FreeMask{~0_u64}; | |||||
ALbuffer *Buffers{nullptr}; /* 64 */ | |||||
BufferSubList() noexcept = default; | |||||
BufferSubList(const BufferSubList&) = delete; | |||||
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} | |||||
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } | |||||
~BufferSubList(); | |||||
BufferSubList& operator=(const BufferSubList&) = delete; | |||||
BufferSubList& operator=(BufferSubList&& rhs) noexcept | |||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } | |||||
}; | |||||
struct EffectSubList { | |||||
uint64_t FreeMask{~0_u64}; | |||||
ALeffect *Effects{nullptr}; /* 64 */ | |||||
EffectSubList() noexcept = default; | |||||
EffectSubList(const EffectSubList&) = delete; | |||||
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} | |||||
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } | |||||
~EffectSubList(); | |||||
EffectSubList& operator=(const EffectSubList&) = delete; | |||||
EffectSubList& operator=(EffectSubList&& rhs) noexcept | |||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } | |||||
}; | |||||
struct FilterSubList { | |||||
uint64_t FreeMask{~0_u64}; | |||||
ALfilter *Filters{nullptr}; /* 64 */ | |||||
FilterSubList() noexcept = default; | |||||
FilterSubList(const FilterSubList&) = delete; | |||||
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} | |||||
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } | |||||
~FilterSubList(); | |||||
FilterSubList& operator=(const FilterSubList&) = delete; | |||||
FilterSubList& operator=(FilterSubList&& rhs) noexcept | |||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } | |||||
}; | |||||
struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase { | |||||
/* This lock protects the device state (format, update size, etc) from | |||||
* being from being changed in multiple threads, or being accessed while | |||||
* being changed. It's also used to serialize calls to the backend. | |||||
*/ | |||||
std::mutex StateLock; | |||||
std::unique_ptr<BackendBase> Backend; | |||||
ALCuint NumMonoSources{}; | |||||
ALCuint NumStereoSources{}; | |||||
// Maximum number of sources that can be created | |||||
uint SourcesMax{}; | |||||
// Maximum number of slots that can be created | |||||
uint AuxiliaryEffectSlotMax{}; | |||||
std::string mHrtfName; | |||||
al::vector<std::string> mHrtfList; | |||||
ALCenum mHrtfStatus{ALC_FALSE}; | |||||
enum class OutputMode1 : ALCenum { | |||||
Any = ALC_ANY_SOFT, | |||||
Mono = ALC_MONO_SOFT, | |||||
Stereo = ALC_STEREO_SOFT, | |||||
StereoBasic = ALC_STEREO_BASIC_SOFT, | |||||
Uhj2 = ALC_STEREO_UHJ_SOFT, | |||||
Hrtf = ALC_STEREO_HRTF_SOFT, | |||||
Quad = ALC_QUAD_SOFT, | |||||
X51 = ALC_SURROUND_5_1_SOFT, | |||||
X61 = ALC_SURROUND_6_1_SOFT, | |||||
X71 = ALC_SURROUND_7_1_SOFT | |||||
}; | |||||
OutputMode1 getOutputMode1() const noexcept; | |||||
using OutputMode = OutputMode1; | |||||
std::atomic<ALCenum> LastError{ALC_NO_ERROR}; | |||||
// Map of Buffers for this device | |||||
std::mutex BufferLock; | |||||
al::vector<BufferSubList> BufferList; | |||||
// Map of Effects for this device | |||||
std::mutex EffectLock; | |||||
al::vector<EffectSubList> EffectList; | |||||
// Map of Filters for this device | |||||
std::mutex FilterLock; | |||||
al::vector<FilterSubList> FilterList; | |||||
#ifdef ALSOFT_EAX | |||||
ALuint eax_x_ram_free_size{eax_x_ram_max_size}; | |||||
#endif // ALSOFT_EAX | |||||
ALCdevice(DeviceType type); | |||||
~ALCdevice(); | |||||
void enumerateHrtfs(); | |||||
bool getConfigValueBool(const char *block, const char *key, bool def) | |||||
{ return GetConfigValueBool(DeviceName.c_str(), block, key, def); } | |||||
template<typename T> | |||||
al::optional<T> configValue(const char *block, const char *key) = delete; | |||||
DEF_NEWDEL(ALCdevice) | |||||
}; | |||||
template<> | |||||
inline al::optional<std::string> ALCdevice::configValue(const char *block, const char *key) | |||||
{ return ConfigValueStr(DeviceName.c_str(), block, key); } | |||||
template<> | |||||
inline al::optional<int> ALCdevice::configValue(const char *block, const char *key) | |||||
{ return ConfigValueInt(DeviceName.c_str(), block, key); } | |||||
template<> | |||||
inline al::optional<uint> ALCdevice::configValue(const char *block, const char *key) | |||||
{ return ConfigValueUInt(DeviceName.c_str(), block, key); } | |||||
template<> | |||||
inline al::optional<float> ALCdevice::configValue(const char *block, const char *key) | |||||
{ return ConfigValueFloat(DeviceName.c_str(), block, key); } | |||||
template<> | |||||
inline al::optional<bool> ALCdevice::configValue(const char *block, const char *key) | |||||
{ return ConfigValueBool(DeviceName.c_str(), block, key); } | |||||
#endif |
@ -0,0 +1,612 @@ | |||||
#include "config.h" | |||||
#include <algorithm> | |||||
#include <array> | |||||
#include <complex> | |||||
#include <cstddef> | |||||
#include <functional> | |||||
#include <iterator> | |||||
#include <memory> | |||||
#include <stdint.h> | |||||
#include <utility> | |||||
#ifdef HAVE_SSE_INTRINSICS | |||||
#include <xmmintrin.h> | |||||
#elif defined(HAVE_NEON) | |||||
#include <arm_neon.h> | |||||
#endif | |||||
#include "albyte.h" | |||||
#include "alcomplex.h" | |||||
#include "almalloc.h" | |||||
#include "alnumbers.h" | |||||
#include "alnumeric.h" | |||||
#include "alspan.h" | |||||
#include "base.h" | |||||
#include "core/ambidefs.h" | |||||
#include "core/bufferline.h" | |||||
#include "core/buffer_storage.h" | |||||
#include "core/context.h" | |||||
#include "core/devformat.h" | |||||
#include "core/device.h" | |||||
#include "core/effectslot.h" | |||||
#include "core/filters/splitter.h" | |||||
#include "core/fmt_traits.h" | |||||
#include "core/mixer.h" | |||||
#include "intrusive_ptr.h" | |||||
#include "polyphase_resampler.h" | |||||
#include "vector.h" | |||||
namespace { | |||||
/* Convolution reverb is implemented using a segmented overlap-add method. The | |||||
* impulse response is broken up into multiple segments of 128 samples, and | |||||
* each segment has an FFT applied with a 256-sample buffer (the latter half | |||||
* left silent) to get its frequency-domain response. The resulting response | |||||
* has its positive/non-mirrored frequencies saved (129 bins) in each segment. | |||||
* | |||||
* Input samples are similarly broken up into 128-sample segments, with an FFT | |||||
* applied to each new incoming segment to get its 129 bins. A history of FFT'd | |||||
* input segments is maintained, equal to the length of the impulse response. | |||||
* | |||||
* To apply the reverberation, each impulse response segment is convolved with | |||||
* its paired input segment (using complex multiplies, far cheaper than FIRs), | |||||
* accumulating into a 256-bin FFT buffer. The input history is then shifted to | |||||
* align with later impulse response segments for next time. | |||||
* | |||||
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256- | |||||
* sample time-domain response for output, which is split in two halves. The | |||||
* first half is the 128-sample output, and the second half is a 128-sample | |||||
* (really, 127) delayed extension, which gets added to the output next time. | |||||
* Convolving two time-domain responses of lengths N and M results in a time- | |||||
* domain signal of length N+M-1, and this holds true regardless of the | |||||
* convolution being applied in the frequency domain, so these "overflow" | |||||
* samples need to be accounted for. | |||||
* | |||||
* To avoid a delay with gathering enough input samples to apply an FFT with, | |||||
* the first segment is applied directly in the time-domain as the samples come | |||||
* in. Once enough have been retrieved, the FFT is applied on the input and | |||||
* it's paired with the remaining (FFT'd) filter segments for processing. | |||||
*/ | |||||
void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, | |||||
const size_t samples) noexcept | |||||
{ | |||||
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break | |||||
switch(srctype) | |||||
{ | |||||
HANDLE_FMT(FmtUByte); | |||||
HANDLE_FMT(FmtShort); | |||||
HANDLE_FMT(FmtFloat); | |||||
HANDLE_FMT(FmtDouble); | |||||
HANDLE_FMT(FmtMulaw); | |||||
HANDLE_FMT(FmtAlaw); | |||||
} | |||||
#undef HANDLE_FMT | |||||
} | |||||
inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept | |||||
{ | |||||
switch(scaletype) | |||||
{ | |||||
case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); | |||||
case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); | |||||
case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); | |||||
case AmbiScaling::N3D: break; | |||||
} | |||||
return AmbiScale::FromN3D(); | |||||
} | |||||
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept | |||||
{ | |||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); | |||||
return AmbiIndex::FromACN(); | |||||
} | |||||
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept | |||||
{ | |||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); | |||||
return AmbiIndex::FromACN2D(); | |||||
} | |||||
struct ChanMap { | |||||
Channel channel; | |||||
float angle; | |||||
float elevation; | |||||
}; | |||||
constexpr float Deg2Rad(float x) noexcept | |||||
{ return static_cast<float>(al::numbers::pi / 180.0 * x); } | |||||
using complex_d = std::complex<double>; | |||||
constexpr size_t ConvolveUpdateSize{256}; | |||||
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; | |||||
void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter) | |||||
{ | |||||
#ifdef HAVE_SSE_INTRINSICS | |||||
for(float &output : dst) | |||||
{ | |||||
__m128 r4{_mm_setzero_ps()}; | |||||
for(size_t j{0};j < ConvolveUpdateSamples;j+=4) | |||||
{ | |||||
const __m128 coeffs{_mm_load_ps(&filter[j])}; | |||||
const __m128 s{_mm_loadu_ps(&src[j])}; | |||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); | |||||
} | |||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); | |||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); | |||||
output = _mm_cvtss_f32(r4); | |||||
++src; | |||||
} | |||||
#elif defined(HAVE_NEON) | |||||
for(float &output : dst) | |||||
{ | |||||
float32x4_t r4{vdupq_n_f32(0.0f)}; | |||||
for(size_t j{0};j < ConvolveUpdateSamples;j+=4) | |||||
r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); | |||||
r4 = vaddq_f32(r4, vrev64q_f32(r4)); | |||||
output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); | |||||
++src; | |||||
} | |||||
#else | |||||
for(float &output : dst) | |||||
{ | |||||
float ret{0.0f}; | |||||
for(size_t j{0};j < ConvolveUpdateSamples;++j) | |||||
ret += src[j] * filter[j]; | |||||
output = ret; | |||||
++src; | |||||
} | |||||
#endif | |||||
} | |||||
struct ConvolutionState final : public EffectState { | |||||
FmtChannels mChannels{}; | |||||
AmbiLayout mAmbiLayout{}; | |||||
AmbiScaling mAmbiScaling{}; | |||||
uint mAmbiOrder{}; | |||||
size_t mFifoPos{0}; | |||||
std::array<float,ConvolveUpdateSamples*2> mInput{}; | |||||
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter; | |||||
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput; | |||||
alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{}; | |||||
size_t mCurrentSegment{0}; | |||||
size_t mNumConvolveSegs{0}; | |||||
struct ChannelData { | |||||
alignas(16) FloatBufferLine mBuffer{}; | |||||
float mHfScale{}; | |||||
BandSplitter mFilter{}; | |||||
float Current[MAX_OUTPUT_CHANNELS]{}; | |||||
float Target[MAX_OUTPUT_CHANNELS]{}; | |||||
}; | |||||
using ChannelDataArray = al::FlexArray<ChannelData>; | |||||
std::unique_ptr<ChannelDataArray> mChans; | |||||
std::unique_ptr<complex_d[]> mComplexData; | |||||
ConvolutionState() = default; | |||||
~ConvolutionState() override = default; | |||||
void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); | |||||
void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); | |||||
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t) | |||||
{&ConvolutionState::NormalMix}; | |||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; | |||||
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, | |||||
const EffectTarget target) override; | |||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, | |||||
const al::span<FloatBufferLine> samplesOut) override; | |||||
DEF_NEWDEL(ConvolutionState) | |||||
}; | |||||
void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut, | |||||
const size_t samplesToDo) | |||||
{ | |||||
for(auto &chan : *mChans) | |||||
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target, | |||||
samplesToDo, 0); | |||||
} | |||||
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut, | |||||
const size_t samplesToDo) | |||||
{ | |||||
for(auto &chan : *mChans) | |||||
{ | |||||
const al::span<float> src{chan.mBuffer.data(), samplesToDo}; | |||||
chan.mFilter.processHfScale(src, chan.mHfScale); | |||||
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); | |||||
} | |||||
} | |||||
void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) | |||||
{ | |||||
constexpr uint MaxConvolveAmbiOrder{1u}; | |||||
mFifoPos = 0; | |||||
mInput.fill(0.0f); | |||||
decltype(mFilter){}.swap(mFilter); | |||||
decltype(mOutput){}.swap(mOutput); | |||||
mFftBuffer.fill(complex_d{}); | |||||
mCurrentSegment = 0; | |||||
mNumConvolveSegs = 0; | |||||
mChans = nullptr; | |||||
mComplexData = nullptr; | |||||
/* An empty buffer doesn't need a convolution filter. */ | |||||
if(!buffer.storage || buffer.storage->mSampleLen < 1) return; | |||||
constexpr size_t m{ConvolveUpdateSize/2 + 1}; | |||||
auto bytesPerSample = BytesFromFmt(buffer.storage->mType); | |||||
auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder); | |||||
auto numChannels = ChannelsFromFmt(buffer.storage->mChannels, | |||||
minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder)); | |||||
mChans = ChannelDataArray::Create(numChannels); | |||||
/* The impulse response needs to have the same sample rate as the input and | |||||
* output. The bsinc24 resampler is decent, but there is high-frequency | |||||
* attenation that some people may be able to pick up on. Since this is | |||||
* called very infrequently, go ahead and use the polyphase resampler. | |||||
*/ | |||||
PPhaseResampler resampler; | |||||
if(device->Frequency != buffer.storage->mSampleRate) | |||||
resampler.init(buffer.storage->mSampleRate, device->Frequency); | |||||
const auto resampledCount = static_cast<uint>( | |||||
(uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) / | |||||
buffer.storage->mSampleRate); | |||||
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)}; | |||||
for(auto &e : *mChans) | |||||
e.mFilter = splitter; | |||||
mFilter.resize(numChannels, {}); | |||||
mOutput.resize(numChannels, {}); | |||||
/* Calculate the number of segments needed to hold the impulse response and | |||||
* the input history (rounded up), and allocate them. Exclude one segment | |||||
* which gets applied as a time-domain FIR filter. Make sure at least one | |||||
* segment is allocated to simplify handling. | |||||
*/ | |||||
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; | |||||
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; | |||||
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; | |||||
mComplexData = std::make_unique<complex_d[]>(complex_length); | |||||
std::fill_n(mComplexData.get(), complex_length, complex_d{}); | |||||
mChannels = buffer.storage->mChannels; | |||||
mAmbiLayout = buffer.storage->mAmbiLayout; | |||||
mAmbiScaling = buffer.storage->mAmbiScaling; | |||||
mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); | |||||
auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount)); | |||||
complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m; | |||||
for(size_t c{0};c < numChannels;++c) | |||||
{ | |||||
/* Load the samples from the buffer, and resample to match the device. */ | |||||
LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels, | |||||
buffer.storage->mType, buffer.storage->mSampleLen); | |||||
if(device->Frequency != buffer.storage->mSampleRate) | |||||
resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount, | |||||
srcsamples.get()); | |||||
/* Store the first segment's samples in reverse in the time-domain, to | |||||
* apply as a FIR filter. | |||||
*/ | |||||
const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; | |||||
std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(), | |||||
[](const double d) noexcept -> float { return static_cast<float>(d); }); | |||||
size_t done{first_size}; | |||||
for(size_t s{0};s < mNumConvolveSegs;++s) | |||||
{ | |||||
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; | |||||
auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin()); | |||||
done += todo; | |||||
std::fill(iter, mFftBuffer.end(), complex_d{}); | |||||
forward_fft(mFftBuffer); | |||||
filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter); | |||||
} | |||||
} | |||||
} | |||||
void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, | |||||
const EffectProps* /*props*/, const EffectTarget target) | |||||
{ | |||||
/* NOTE: Stereo and Rear are slightly different from normal mixing (as | |||||
* defined in alu.cpp). These are 45 degrees from center, rather than the | |||||
* 30 degrees used there. | |||||
* | |||||
* TODO: LFE is not mixed to output. This will require each buffer channel | |||||
* to have its own output target since the main mixing buffer won't have an | |||||
* LFE channel (due to being B-Format). | |||||
*/ | |||||
static constexpr ChanMap MonoMap[1]{ | |||||
{ FrontCenter, 0.0f, 0.0f } | |||||
}, StereoMap[2]{ | |||||
{ FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, | |||||
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) } | |||||
}, RearMap[2]{ | |||||
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, | |||||
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } | |||||
}, QuadMap[4]{ | |||||
{ FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, | |||||
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, | |||||
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, | |||||
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } | |||||
}, X51Map[6]{ | |||||
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, | |||||
{ LFE, 0.0f, 0.0f }, | |||||
{ SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, | |||||
{ SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } | |||||
}, X61Map[7]{ | |||||
{ FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, | |||||
{ LFE, 0.0f, 0.0f }, | |||||
{ BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, | |||||
{ SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, | |||||
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } | |||||
}, X71Map[8]{ | |||||
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, | |||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, | |||||
{ LFE, 0.0f, 0.0f }, | |||||
{ BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, | |||||
{ BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, | |||||
{ SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, | |||||
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } | |||||
}; | |||||
if(mNumConvolveSegs < 1) | |||||
return; | |||||
mMix = &ConvolutionState::NormalMix; | |||||
for(auto &chan : *mChans) | |||||
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); | |||||
const float gain{slot->Gain}; | |||||
/* TODO: UHJ should be decoded to B-Format and processed that way, since | |||||
* there's no telling if it can ever do a direct-out mix (even if the | |||||
* device is outputing UHJ, the effect slot can feed another effect that's | |||||
* not UHJ). | |||||
* | |||||
* Not that UHJ should really ever be used for convolution, but it's a | |||||
* valid format regardless. | |||||
*/ | |||||
if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut | |||||
&& target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX | |||||
&& target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX) | |||||
{ | |||||
mOutTarget = target.RealOut->Buffer; | |||||
const uint lidx = target.RealOut->ChannelIndex[FrontLeft]; | |||||
const uint ridx = target.RealOut->ChannelIndex[FrontRight]; | |||||
(*mChans)[0].Target[lidx] = gain; | |||||
(*mChans)[1].Target[ridx] = gain; | |||||
} | |||||
else if(IsBFormat(mChannels)) | |||||
{ | |||||
DeviceBase *device{context->mDevice}; | |||||
if(device->mAmbiOrder > mAmbiOrder) | |||||
{ | |||||
mMix = &ConvolutionState::UpsampleMix; | |||||
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); | |||||
(*mChans)[0].mHfScale = scales[0]; | |||||
for(size_t i{1};i < mChans->size();++i) | |||||
(*mChans)[i].mHfScale = scales[1]; | |||||
} | |||||
mOutTarget = target.Main->Buffer; | |||||
auto&& scales = GetAmbiScales(mAmbiScaling); | |||||
const uint8_t *index_map{(mChannels == FmtBFormat2D) ? | |||||
GetAmbi2DLayout(mAmbiLayout).data() : | |||||
GetAmbiLayout(mAmbiLayout).data()}; | |||||
std::array<float,MaxAmbiChannels> coeffs{}; | |||||
for(size_t c{0u};c < mChans->size();++c) | |||||
{ | |||||
const size_t acn{index_map[c]}; | |||||
coeffs[acn] = scales[acn]; | |||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target); | |||||
coeffs[acn] = 0.0f; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
DeviceBase *device{context->mDevice}; | |||||
al::span<const ChanMap> chanmap{}; | |||||
switch(mChannels) | |||||
{ | |||||
case FmtMono: chanmap = MonoMap; break; | |||||
case FmtSuperStereo: | |||||
case FmtStereo: chanmap = StereoMap; break; | |||||
case FmtRear: chanmap = RearMap; break; | |||||
case FmtQuad: chanmap = QuadMap; break; | |||||
case FmtX51: chanmap = X51Map; break; | |||||
case FmtX61: chanmap = X61Map; break; | |||||
case FmtX71: chanmap = X71Map; break; | |||||
case FmtBFormat2D: | |||||
case FmtBFormat3D: | |||||
case FmtUHJ2: | |||||
case FmtUHJ3: | |||||
case FmtUHJ4: | |||||
break; | |||||
} | |||||
mOutTarget = target.Main->Buffer; | |||||
if(device->mRenderMode == RenderMode::Pairwise) | |||||
{ | |||||
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float | |||||
{ | |||||
constexpr float half_pi{al::numbers::pi_v<float>*0.5f}; | |||||
const float abs_azi{std::fabs(azimuth)}; | |||||
if(!(abs_azi >= half_pi)) | |||||
return std::copysign(minf(abs_azi*scale, half_pi), azimuth); | |||||
return azimuth; | |||||
}; | |||||
for(size_t i{0};i < chanmap.size();++i) | |||||
{ | |||||
if(chanmap[i].channel == LFE) continue; | |||||
const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f), | |||||
chanmap[i].elevation, 0.0f); | |||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); | |||||
} | |||||
} | |||||
else for(size_t i{0};i < chanmap.size();++i) | |||||
{ | |||||
if(chanmap[i].channel == LFE) continue; | |||||
const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f); | |||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); | |||||
} | |||||
} | |||||
} | |||||
void ConvolutionState::process(const size_t samplesToDo, | |||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) | |||||
{ | |||||
if(mNumConvolveSegs < 1) | |||||
return; | |||||
constexpr size_t m{ConvolveUpdateSize/2 + 1}; | |||||
size_t curseg{mCurrentSegment}; | |||||
auto &chans = *mChans; | |||||
for(size_t base{0u};base < samplesToDo;) | |||||
{ | |||||
const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; | |||||
std::copy_n(samplesIn[0].begin() + base, todo, | |||||
mInput.begin()+ConvolveUpdateSamples+mFifoPos); | |||||
/* Apply the FIR for the newly retrieved input samples, and combine it | |||||
* with the inverse FFT'd output samples. | |||||
*/ | |||||
for(size_t c{0};c < chans.size();++c) | |||||
{ | |||||
auto buf_iter = chans[c].mBuffer.begin() + base; | |||||
apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos, | |||||
mFilter[c].data()); | |||||
auto fifo_iter = mOutput[c].begin() + mFifoPos; | |||||
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); | |||||
} | |||||
mFifoPos += todo; | |||||
base += todo; | |||||
/* Check whether the input buffer is filled with new samples. */ | |||||
if(mFifoPos < ConvolveUpdateSamples) break; | |||||
mFifoPos = 0; | |||||
/* Move the newest input to the front for the next iteration's history. */ | |||||
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); | |||||
/* Calculate the frequency domain response and add the relevant | |||||
* frequency bins to the FFT history. | |||||
*/ | |||||
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); | |||||
std::fill(fftiter, mFftBuffer.end(), complex_d{}); | |||||
forward_fft(mFftBuffer); | |||||
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); | |||||
const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; | |||||
for(size_t c{0};c < chans.size();++c) | |||||
{ | |||||
std::fill_n(mFftBuffer.begin(), m, complex_d{}); | |||||
/* Convolve each input segment with its IR filter counterpart | |||||
* (aligned in time). | |||||
*/ | |||||
const complex_d *RESTRICT input{&mComplexData[curseg*m]}; | |||||
for(size_t s{curseg};s < mNumConvolveSegs;++s) | |||||
{ | |||||
for(size_t i{0};i < m;++i,++input,++filter) | |||||
mFftBuffer[i] += *input * *filter; | |||||
} | |||||
input = mComplexData.get(); | |||||
for(size_t s{0};s < curseg;++s) | |||||
{ | |||||
for(size_t i{0};i < m;++i,++input,++filter) | |||||
mFftBuffer[i] += *input * *filter; | |||||
} | |||||
/* Reconstruct the mirrored/negative frequencies to do a proper | |||||
* inverse FFT. | |||||
*/ | |||||
for(size_t i{m};i < ConvolveUpdateSize;++i) | |||||
mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]); | |||||
/* Apply iFFT to get the 256 (really 255) samples for output. The | |||||
* 128 output samples are combined with the last output's 127 | |||||
* second-half samples (and this output's second half is | |||||
* subsequently saved for next time). | |||||
*/ | |||||
inverse_fft(mFftBuffer); | |||||
/* The iFFT'd response is scaled up by the number of bins, so apply | |||||
* the inverse to normalize the output. | |||||
*/ | |||||
for(size_t i{0};i < ConvolveUpdateSamples;++i) | |||||
mOutput[c][i] = | |||||
static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) + | |||||
mOutput[c][ConvolveUpdateSamples+i]; | |||||
for(size_t i{0};i < ConvolveUpdateSamples;++i) | |||||
mOutput[c][ConvolveUpdateSamples+i] = | |||||
static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() * | |||||
(1.0/double{ConvolveUpdateSize})); | |||||
} | |||||
/* Shift the input history. */ | |||||
curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1); | |||||
} | |||||
mCurrentSegment = curseg; | |||||
/* Finally, mix to the output. */ | |||||
(this->*mMix)(samplesOut, samplesToDo); | |||||
} | |||||
struct ConvolutionStateFactory final : public EffectStateFactory { | |||||
al::intrusive_ptr<EffectState> create() override | |||||
{ return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; } | |||||
}; | |||||
} // namespace | |||||
EffectStateFactory *ConvolutionStateFactory_getFactory() | |||||
{ | |||||
static ConvolutionStateFactory ConvolutionFactory{}; | |||||
return &ConvolutionFactory; | |||||
} |
@ -0,0 +1,337 @@ | |||||
/** | |||||
* This file is part of the OpenAL Soft cross platform audio library | |||||
* | |||||
* Copyright (C) 2019 by Anis A. Hireche | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* | |||||
* * Redistributions of source code must retain the above copyright notice, | |||||
* this list of conditions and the following disclaimer. | |||||
* | |||||
* * Redistributions in binary form must reproduce the above copyright notice, | |||||
* this list of conditions and the following disclaimer in the documentation | |||||
* and/or other materials provided with the distribution. | |||||
* | |||||
* * Neither the name of Spherical-Harmonic-Transform nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||||
* POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#include "config.h" | |||||
#include <algorithm> | |||||
#include <array> | |||||
#include <cstdlib> | |||||
#include <functional> | |||||
#include <iterator> | |||||
#include "alc/effects/base.h" | |||||
#include "almalloc.h" | |||||
#include "alnumbers.h" | |||||
#include "alnumeric.h" | |||||
#include "alspan.h" | |||||
#include "core/ambidefs.h" | |||||
#include "core/bufferline.h" | |||||
#include "core/context.h" | |||||
#include "core/devformat.h" | |||||
#include "core/device.h" | |||||
#include "core/effectslot.h" | |||||
#include "core/mixer.h" | |||||
#include "intrusive_ptr.h" | |||||
namespace { | |||||
using uint = unsigned int; | |||||
#define MAX_UPDATE_SAMPLES 256 | |||||
#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 float Sin(uint index) | |||||
{ | |||||
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE}; | |||||
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f; | |||||
} | |||||
inline float Saw(uint index) | |||||
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; } | |||||
inline float Triangle(uint index) | |||||
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); } | |||||
inline float Half(uint) { return 0.5f; } | |||||
template<float (&func)(uint)> | |||||
void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo) | |||||
{ | |||||
for(size_t i{0u};i < todo;i++) | |||||
{ | |||||
index += step; | |||||
index &= WAVEFORM_FRACMASK; | |||||
dst[i] = func(index); | |||||
} | |||||
} | |||||
struct FormantFilter | |||||
{ | |||||
float mCoeff{0.0f}; | |||||
float mGain{1.0f}; | |||||
float mS1{0.0f}; | |||||
float mS2{0.0f}; | |||||
FormantFilter() = default; | |||||
FormantFilter(float f0norm, float gain) | |||||
: mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain} | |||||
{ } | |||||
inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) | |||||
{ | |||||
/* A state variable filter from a topology-preserving transform. | |||||
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg | |||||
*/ | |||||
const float g{mCoeff}; | |||||
const float gain{mGain}; | |||||
const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; | |||||
float s1{mS1}; | |||||
float s2{mS2}; | |||||
for(size_t i{0u};i < numInput;i++) | |||||
{ | |||||
const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; | |||||
const float B{g*H + s1}; | |||||
const float L{g*B + s2}; | |||||
s1 = g*H + B; | |||||
s2 = g*B + L; | |||||
// Apply peak and accumulate samples. | |||||
samplesOut[i] += B * gain; | |||||
} | |||||
mS1 = s1; | |||||
mS2 = s2; | |||||
} | |||||
inline void clear() | |||||
{ | |||||
mS1 = 0.0f; | |||||
mS2 = 0.0f; | |||||
} | |||||
}; | |||||
struct VmorpherState final : public EffectState { | |||||
struct { | |||||
/* Effect parameters */ | |||||
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; | |||||
/* Effect gains for each channel */ | |||||
float CurrentGains[MAX_OUTPUT_CHANNELS]{}; | |||||
float TargetGains[MAX_OUTPUT_CHANNELS]{}; | |||||
} mChans[MaxAmbiChannels]; | |||||
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; | |||||
uint mIndex{0}; | |||||
uint mStep{1}; | |||||
/* Effects buffers */ | |||||
alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{}; | |||||
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; | |||||
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; | |||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; | |||||
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, | |||||
const EffectTarget target) override; | |||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, | |||||
const al::span<FloatBufferLine> samplesOut) override; | |||||
static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme, | |||||
float frequency, float pitch); | |||||
DEF_NEWDEL(VmorpherState) | |||||
}; | |||||
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, | |||||
float frequency, float pitch) | |||||
{ | |||||
/* Using soprano formant set of values to | |||||
* better match mid-range frequency space. | |||||
* | |||||
* See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html | |||||
*/ | |||||
switch(phoneme) | |||||
{ | |||||
case VMorpherPhenome::A: | |||||
return {{ | |||||
{( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ | |||||
{(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */ | |||||
{(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */ | |||||
{(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */ | |||||
}}; | |||||
case VMorpherPhenome::E: | |||||
return {{ | |||||
{( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ | |||||
{(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */ | |||||
{(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */ | |||||
{(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ | |||||
}}; | |||||
case VMorpherPhenome::I: | |||||
return {{ | |||||
{( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ | |||||
{(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */ | |||||
{(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */ | |||||
{(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */ | |||||
}}; | |||||
case VMorpherPhenome::O: | |||||
return {{ | |||||
{( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ | |||||
{( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */ | |||||
{(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */ | |||||
{(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */ | |||||
}}; | |||||
case VMorpherPhenome::U: | |||||
return {{ | |||||
{( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ | |||||
{( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */ | |||||
{(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */ | |||||
{(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ | |||||
}}; | |||||
default: | |||||
break; | |||||
} | |||||
return {}; | |||||
} | |||||
void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) | |||||
{ | |||||
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); | |||||
} | |||||
} | |||||
void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, | |||||
const EffectProps *props, const EffectTarget target) | |||||
{ | |||||
const DeviceBase *device{context->mDevice}; | |||||
const float frequency{static_cast<float>(device->Frequency)}; | |||||
const float step{props->Vmorpher.Rate / frequency}; | |||||
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); | |||||
if(mStep == 0) | |||||
mGetSamples = Oscillate<Half>; | |||||
else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid) | |||||
mGetSamples = Oscillate<Sin>; | |||||
else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle) | |||||
mGetSamples = Oscillate<Triangle>; | |||||
else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/ | |||||
mGetSamples = Oscillate<Saw>; | |||||
const float pitchA{std::pow(2.0f, | |||||
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)}; | |||||
const float pitchB{std::pow(2.0f, | |||||
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)}; | |||||
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; | |||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs) | |||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; | |||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); | |||||
} | |||||
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, 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(size_t base{0u};base < samplesToDo;) | |||||
{ | |||||
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; | |||||
mGetSamples(mLfo, mIndex, mStep, td); | |||||
mIndex += static_cast<uint>(mStep * td); | |||||
mIndex &= WAVEFORM_FRACMASK; | |||||
auto chandata = std::begin(mChans); | |||||
for(const auto &input : samplesIn) | |||||
{ | |||||
auto& vowelA = chandata->Formants[VOWEL_A_INDEX]; | |||||
auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; | |||||
/* Process first vowel. */ | |||||
std::fill_n(std::begin(mSampleBufferA), td, 0.0f); | |||||
vowelA[0].process(&input[base], mSampleBufferA, td); | |||||
vowelA[1].process(&input[base], mSampleBufferA, td); | |||||
vowelA[2].process(&input[base], mSampleBufferA, td); | |||||
vowelA[3].process(&input[base], mSampleBufferA, td); | |||||
/* Process second vowel. */ | |||||
std::fill_n(std::begin(mSampleBufferB), td, 0.0f); | |||||
vowelB[0].process(&input[base], mSampleBufferB, td); | |||||
vowelB[1].process(&input[base], mSampleBufferB, td); | |||||
vowelB[2].process(&input[base], mSampleBufferB, td); | |||||
vowelB[3].process(&input[base], mSampleBufferB, td); | |||||
alignas(16) float blended[MAX_UPDATE_SAMPLES]; | |||||
for(size_t i{0u};i < td;i++) | |||||
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); | |||||
/* Now, mix the processed sound data to the output. */ | |||||
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, | |||||
samplesToDo-base, base); | |||||
++chandata; | |||||
} | |||||
base += td; | |||||
} | |||||
} | |||||
struct VmorpherStateFactory final : public EffectStateFactory { | |||||
al::intrusive_ptr<EffectState> create() override | |||||
{ return al::intrusive_ptr<EffectState>{new VmorpherState{}}; } | |||||
}; | |||||
} // namespace | |||||
EffectStateFactory *VmorpherStateFactory_getFactory() | |||||
{ | |||||
static VmorpherStateFactory VmorpherFactory{}; | |||||
return &VmorpherFactory; | |||||
} |
@ -1,137 +0,0 @@ | |||||
#ifndef FILTERS_BIQUAD_H | |||||
#define FILTERS_BIQUAD_H | |||||
#include <cmath> | |||||
#include <utility> | |||||
#include "AL/al.h" | |||||
#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 calc_rcpQ_from_slope | |||||
* or calc_rcpQ_from_bandwidth 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); | |||||
void passthru(int numsamples) noexcept | |||||
{ | |||||
if(LIKELY(numsamples >= 2)) | |||||
{ | |||||
z1 = 0.0f; | |||||
z2 = 0.0f; | |||||
} | |||||
else if(numsamples == 1) | |||||
{ | |||||
z1 = z2; | |||||
z2 = 0.0f; | |||||
} | |||||
} | |||||
/* 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; | |||||
} | |||||
}; | |||||
using BiquadFilter = BiquadFilterR<float>; | |||||
/** | |||||
* 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 | |||||
*/ | |||||
inline float calc_rcpQ_from_slope(float gain, float slope) | |||||
{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); } | |||||
inline double calc_rcpQ_from_slope(double gain, double slope) | |||||
{ return std::sqrt((gain + 1.0/gain)*(1.0/slope - 1.0) + 2.0); } | |||||
/** | |||||
* 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 | |||||
*/ | |||||
inline float calc_rcpQ_from_bandwidth(float f0norm, float bandwidth) | |||||
{ | |||||
const float w0{al::MathDefs<float>::Tau() * f0norm}; | |||||
return 2.0f*std::sinh(std::log(2.0f)/2.0f*bandwidth*w0/std::sin(w0)); | |||||
} | |||||
inline double calc_rcpQ_from_bandwidth(double f0norm, double bandwidth) | |||||
{ | |||||
const double w0{al::MathDefs<double>::Tau() * f0norm}; | |||||
return 2.0*std::sinh(std::log(2.0)/2.0*bandwidth*w0/std::sin(w0)); | |||||
} | |||||
#endif /* FILTERS_BIQUAD_H */ |
@ -1,132 +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 class BandSplitterR<float>; | |||||
template class BandSplitterR<double>; | |||||
template<typename Real> | |||||
void SplitterAllpassR<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; | |||||
z1 = 0.0f; | |||||
} | |||||
template<typename Real> | |||||
void SplitterAllpassR<Real>::process(Real *samples, int count) | |||||
{ | |||||
ASSUME(count > 0); | |||||
const Real coeff{this->coeff}; | |||||
Real z1{this->z1}; | |||||
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); | |||||
this->z1 = z1; | |||||
} | |||||
template class SplitterAllpassR<float>; | |||||
template class SplitterAllpassR<double>; |
@ -1,63 +0,0 @@ | |||||
#ifndef FILTER_SPLITTER_H | |||||
#define FILTER_SPLITTER_H | |||||
#include "alMain.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); | |||||
}; | |||||
using BandSplitter = BandSplitterR<float>; | |||||
/* The all-pass portion of the band splitter. Applies the same phase shift | |||||
* without splitting the signal. | |||||
*/ | |||||
template<typename Real> | |||||
class SplitterAllpassR { | |||||
Real coeff{0.0f}; | |||||
Real z1{0.0f}; | |||||
public: | |||||
SplitterAllpassR() = default; | |||||
SplitterAllpassR(const SplitterAllpassR&) = default; | |||||
SplitterAllpassR(Real f0norm) { init(f0norm); } | |||||
void init(Real f0norm); | |||||
void clear() noexcept { z1 = 0.0f; } | |||||
void process(Real *samples, int count); | |||||
}; | |||||
using SplitterAllpass = SplitterAllpassR<float>; | |||||
struct FrontStablizer { | |||||
static constexpr size_t DelayLength{256u}; | |||||
alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength]; | |||||
SplitterAllpass APFilter; | |||||
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 */ |
@ -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 */ |
@ -1,740 +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 <cstdlib> | |||||
#include <ctime> | |||||
#include <cerrno> | |||||
#include <cstdarg> | |||||
#include <cctype> | |||||
#ifdef HAVE_MALLOC_H | |||||
#include <malloc.h> | |||||
#endif | |||||
#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 | |||||
#ifdef HAVE_FLOAT_H | |||||
#include <cfloat> | |||||
#endif | |||||
#ifdef HAVE_IEEEFP_H | |||||
#include <ieeefp.h> | |||||
#endif | |||||
#ifndef _WIN32 | |||||
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/mman.h> | |||||
#include <fcntl.h> | |||||
#include <unistd.h> | |||||
#elif defined(_WIN32_IE) | |||||
#include <shlobj.h> | |||||
#endif | |||||
#include <mutex> | |||||
#include <vector> | |||||
#include <string> | |||||
#include <algorithm> | |||||
#include "alMain.h" | |||||
#include "alu.h" | |||||
#include "cpu_caps.h" | |||||
#include "fpu_modes.h" | |||||
#include "vector.h" | |||||
#include "compat.h" | |||||
#include "threads.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 | |||||
FILE *file = fopen("/proc/cpuinfo", "rt"); | |||||
if(!file) | |||||
ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n"); | |||||
else | |||||
{ | |||||
std::string features; | |||||
char buf[256]; | |||||
while(fgets(buf, sizeof(buf), file) != nullptr) | |||||
{ | |||||
if(strncmp(buf, "Features\t:", 10) != 0) | |||||
continue; | |||||
features = buf+10; | |||||
while(features.back() != '\n') | |||||
{ | |||||
if(fgets(buf, sizeof(buf), file) == nullptr) | |||||
break; | |||||
features += buf; | |||||
} | |||||
break; | |||||
} | |||||
fclose(file); | |||||
file = nullptr; | |||||
if(!features.empty()) | |||||
{ | |||||
const char *str = features.c_str(); | |||||
while(isspace(str[0])) ++str; | |||||
TRACE("Got features string:%s\n", str); | |||||
while((str=strstr(str, "neon")) != nullptr) | |||||
{ | |||||
if(isspace(*(str-1)) && (str[4] == 0 || isspace(str[4]))) | |||||
{ | |||||
caps |= CPU_CAP_NEON; | |||||
break; | |||||
} | |||||
++str; | |||||
} | |||||
} | |||||
} | |||||
#endif | |||||
TRACE("Extensions:%s%s%s%s%s%s\n", | |||||
((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), | |||||
((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), | |||||
((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), | |||||
((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), | |||||
((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), | |||||
((!capfilter) ? " -none-" : "") | |||||
); | |||||
CPUCapFlags = caps & capfilter; | |||||
} | |||||
FPUCtl::FPUCtl() | |||||
{ | |||||
#if defined(HAVE_SSE_INTRINSICS) | |||||
this->sse_state = _mm_getcsr(); | |||||
unsigned int sseState = this->sse_state; | |||||
sseState |= 0x8000; /* set flush-to-zero */ | |||||
sseState |= 0x0040; /* set denormals-are-zero */ | |||||
_mm_setcsr(sseState); | |||||
#elif defined(__GNUC__) && defined(HAVE_SSE) | |||||
if((CPUCapFlags&CPU_CAP_SSE)) | |||||
{ | |||||
__asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state)); | |||||
unsigned int sseState = this->sse_state; | |||||
sseState |= 0x8000; /* set flush-to-zero */ | |||||
if((CPUCapFlags&CPU_CAP_SSE2)) | |||||
sseState |= 0x0040; /* set denormals-are-zero */ | |||||
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState)); | |||||
} | |||||
#endif | |||||
this->in_mode = true; | |||||
} | |||||
void FPUCtl::leave() | |||||
{ | |||||
if(!this->in_mode) return; | |||||
#if defined(HAVE_SSE_INTRINSICS) | |||||
_mm_setcsr(this->sse_state); | |||||
#elif defined(__GNUC__) && defined(HAVE_SSE) | |||||
if((CPUCapFlags&CPU_CAP_SSE)) | |||||
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state)); | |||||
#endif | |||||
this->in_mode = false; | |||||
} | |||||
#ifdef _WIN32 | |||||
const PathNamePair &GetProcBinary() | |||||
{ | |||||
static PathNamePair ret; | |||||
if(!ret.fname.empty() || !ret.path.empty()) | |||||
return ret; | |||||
al::vector<WCHAR> fullpath(256); | |||||
DWORD len; | |||||
while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))) == fullpath.size()) | |||||
fullpath.resize(fullpath.size() << 1); | |||||
if(len == 0) | |||||
{ | |||||
ERR("Failed to get process name: error %lu\n", GetLastError()); | |||||
return ret; | |||||
} | |||||
fullpath.resize(len); | |||||
if(fullpath.back() != 0) | |||||
fullpath.push_back(0); | |||||
auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); | |||||
sep = std::find(fullpath.rbegin()+1, sep, '/'); | |||||
if(sep != fullpath.rend()) | |||||
{ | |||||
*sep = 0; | |||||
ret.fname = wstr_to_utf8(&*sep + 1); | |||||
ret.path = wstr_to_utf8(fullpath.data()); | |||||
} | |||||
else | |||||
ret.fname = wstr_to_utf8(fullpath.data()); | |||||
TRACE("Got: %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(const char *type, const char *prefix, const char *func, 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(gLogFile, "AL lib: %s %s%s: %ls", type, prefix, func, wstr.c_str()); | |||||
fflush(gLogFile); | |||||
} | |||||
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: %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(const char *type, const char *prefix, const char *func, const char *fmt, ...) | |||||
{ | |||||
va_list ap; | |||||
va_start(ap, fmt); | |||||
fprintf(gLogFile, "AL lib: %s %s%s: ", type, prefix, func); | |||||
vfprintf(gLogFile, fmt, ap); | |||||
va_end(ap); | |||||
fflush(gLogFile); | |||||
} | |||||
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 |
@ -1,121 +0,0 @@ | |||||
#ifndef ALC_HRTF_H | |||||
#define ALC_HRTF_H | |||||
#include <array> | |||||
#include <memory> | |||||
#include <string> | |||||
#include "AL/al.h" | |||||
#include "AL/alc.h" | |||||
#include "vector.h" | |||||
#include "almalloc.h" | |||||
#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 HrtfHandle; | |||||
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(); | |||||
static constexpr inline const char *CurrentPrefix() noexcept { return "HrtfEntry::"; } | |||||
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 HrtfParams { | |||||
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 ALsizei NumChannels, const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain); | |||||
#endif /* ALC_HRTF_H */ |
@ -1,65 +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; | |||||
constexpr inline const char *CurrentPrefix() noexcept { return ""; } | |||||
#if defined(__GNUC__) && !defined(_WIN32) | |||||
#define AL_PRINT(T, MSG, ...) fprintf(gLogFile, "AL lib: %s %s%s: " MSG, T, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__) | |||||
#else | |||||
void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...) DECL_FORMAT(printf, 4,5); | |||||
#define AL_PRINT(T, ...) al_print((T), CurrentPrefix(), __FUNCTION__, __VA_ARGS__) | |||||
#endif | |||||
#ifdef __ANDROID__ | |||||
#include <android/log.h> | |||||
#define LOG_ANDROID(T, MSG, ...) __android_log_print(T, "openal", "AL lib: %s%s: " MSG, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__) | |||||
#else | |||||
#define LOG_ANDROID(T, MSG, ...) ((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 */ |
@ -1,107 +0,0 @@ | |||||
#ifndef MASTERING_H | |||||
#define MASTERING_H | |||||
#include <memory> | |||||
#include "AL/al.h" | |||||
#include "almalloc.h" | |||||
/* For BUFFERSIZE. */ | |||||
#include "alMain.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 { | |||||
ALsizei mNumChans{0}; | |||||
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}; | |||||
ALfloat (*mDelay)[BUFFERSIZE]{nullptr}; | |||||
ALsizei mDelayIndex{0}; | |||||
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, ALfloat (*OutBuffer)[BUFFERSIZE]); | |||||
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 ALsizei 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 */ |
@ -1,57 +0,0 @@ | |||||
#ifndef MIXER_DEFS_H | |||||
#define MIXER_DEFS_H | |||||
#include "AL/alc.h" | |||||
#include "AL/al.h" | |||||
#include "alMain.h" | |||||
#include "alu.h" | |||||
struct MixGains; | |||||
struct MixHrtfParams; | |||||
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 ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize); | |||||
template<typename InstTag> | |||||
void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize); | |||||
template<typename InstTag> | |||||
void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, MixHrtfParams *hrtfparams, const ALsizei BufferSize); | |||||
template<typename InstTag> | |||||
void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize); | |||||
template<typename InstTag> | |||||
void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, const ALsizei NumChans, 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 */ |
@ -1,132 +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(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, | |||||
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
MixHrtfParams *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}; | |||||
ALfloat stepcount{0.0f}; | |||||
ALsizei Delay[2]{ | |||||
HRTF_HISTORY_LENGTH - hrtfparams->Delay[0], | |||||
HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] }; | |||||
ASSUME(Delay[0] >= 0 && Delay[1] >= 0); | |||||
for(ALsizei i{0};i < BufferSize;++i) | |||||
{ | |||||
const ALfloat g{gain + gainstep*stepcount}; | |||||
const ALfloat left{data[Delay[0]++] * g}; | |||||
const ALfloat right{data[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(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
const HrtfParams *oldparams, MixHrtfParams *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}; | |||||
ALfloat stepcount{0.0f}; | |||||
ASSUME(OutPos >= 0); | |||||
ASSUME(IrSize >= 4); | |||||
ASSUME(BufferSize > 0); | |||||
ALsizei OldDelay[2]{ | |||||
HRTF_HISTORY_LENGTH - oldparams->Delay[0], | |||||
HRTF_HISTORY_LENGTH - oldparams->Delay[1] }; | |||||
ASSUME(OldDelay[0] >= 0 && OldDelay[1] >= 0); | |||||
ALsizei NewDelay[2]{ | |||||
HRTF_HISTORY_LENGTH - newparams->Delay[0], | |||||
HRTF_HISTORY_LENGTH - newparams->Delay[1] }; | |||||
ASSUME(NewDelay[0] >= 0 && NewDelay[1] >= 0); | |||||
for(ALsizei i{0};i < BufferSize;++i) | |||||
{ | |||||
ALfloat g{oldGain + oldGainStep*stepcount}; | |||||
ALfloat left{data[OldDelay[0]++] * g}; | |||||
ALfloat right{data[OldDelay[1]++] * g}; | |||||
ApplyCoeffs(i, AccumSamples+i, IrSize, OldCoeffs, left, right); | |||||
g = newGainStep*stepcount; | |||||
left = data[NewDelay[0]++] * g; | |||||
right = data[NewDelay[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(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, | |||||
const ALsizei NumChans, const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(NumChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
const ALsizei IrSize{State->IrSize}; | |||||
ASSUME(IrSize >= 4); | |||||
for(ALsizei c{0};c < NumChans;++c) | |||||
{ | |||||
const ALfloat (&input)[BUFFERSIZE] = data[c]; | |||||
const auto &Coeffs = State->Chan[c].Coeffs; | |||||
auto accum_iter = std::copy_n(State->Chan[c].Values.begin(), | |||||
State->Chan[c].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, State->Chan[c].Values.size(), | |||||
State->Chan[c].Values.begin()); | |||||
} | |||||
} | |||||
#endif /* MIXER_HRTFBASE_H */ |
@ -1,208 +0,0 @@ | |||||
#include "config.h" | |||||
#include <cassert> | |||||
#include <limits> | |||||
#include "alMain.h" | |||||
#include "alu.h" | |||||
#include "alSource.h" | |||||
#include "alAuxEffectSlot.h" | |||||
#include "defs.h" | |||||
#include "hrtfbase.h" | |||||
static inline ALfloat do_point(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei) noexcept | |||||
{ return vals[0]; } | |||||
static inline ALfloat do_lerp(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept | |||||
{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); } | |||||
static inline ALfloat do_cubic(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept | |||||
{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); } | |||||
static inline ALfloat do_bsinc(const InterpState &istate, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept | |||||
{ | |||||
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> | |||||
static 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<ALfloat*RESTRICT>(dst, numsamples, proc_sample); | |||||
return dst; | |||||
} | |||||
template<> | |||||
const ALfloat *Resample_<CopyTag,CTag>(const InterpState* UNUSED(state), | |||||
const ALfloat *RESTRICT src, ALsizei UNUSED(frac), ALint UNUSED(increment), | |||||
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>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, | |||||
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
MixHrtfParams *hrtfparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void MixHrtfBlend_<CTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, | |||||
newparams, BufferSize); | |||||
} | |||||
template<> | |||||
void MixDirectHrtf_<CTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, | |||||
const ALsizei NumChans, const ALsizei BufferSize) | |||||
{ | |||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void Mix_<CTag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], | |||||
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, | |||||
const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(OutChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; | |||||
for(ALsizei c{0};c < OutChans;c++) | |||||
{ | |||||
ALfloat *RESTRICT dst{&OutBuffer[c][OutPos]}; | |||||
ALsizei pos{0}; | |||||
ALfloat gain{CurrentGains[c]}; | |||||
const ALfloat diff{TargetGains[c] - gain}; | |||||
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[c]; | |||||
else | |||||
gain += step*step_count; | |||||
CurrentGains[c] = gain; | |||||
} | |||||
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>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], | |||||
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(InChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
for(ALsizei c{0};c < InChans;c++) | |||||
{ | |||||
const ALfloat *RESTRICT src{&data[c][InPos]}; | |||||
const ALfloat gain{Gains[c]}; | |||||
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) | |||||
continue; | |||||
for(ALsizei i{0};i < BufferSize;i++) | |||||
OutBuffer[i] += src[i] * gain; | |||||
} | |||||
} |
@ -1,309 +0,0 @@ | |||||
#include "config.h" | |||||
#include <arm_neon.h> | |||||
#include <limits> | |||||
#include "AL/al.h" | |||||
#include "AL/alc.h" | |||||
#include "alMain.h" | |||||
#include "alu.h" | |||||
#include "hrtf.h" | |||||
#include "defs.h" | |||||
#include "hrtfbase.h" | |||||
template<> | |||||
const ALfloat *Resample_<LerpTag,NEONTag>(const InterpState* UNUSED(state), | |||||
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>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, | |||||
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
MixHrtfParams *hrtfparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void MixHrtfBlend_<NEONTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, | |||||
newparams, BufferSize); | |||||
} | |||||
template<> | |||||
void MixDirectHrtf_<NEONTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, | |||||
const ALsizei NumChans, const ALsizei BufferSize) | |||||
{ | |||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void Mix_<NEONTag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], | |||||
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, | |||||
const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(OutChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
const ALfloat delta{(Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f}; | |||||
for(ALsizei c{0};c < OutChans;c++) | |||||
{ | |||||
ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])}; | |||||
ALsizei pos{0}; | |||||
ALfloat gain{CurrentGains[c]}; | |||||
const ALfloat diff{TargetGains[c] - gain}; | |||||
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[c]; | |||||
else | |||||
gain += step*step_count; | |||||
CurrentGains[c] = 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; | |||||
} | |||||
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>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], | |||||
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(InChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
for(ALsizei c{0};c < InChans;c++) | |||||
{ | |||||
const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])}; | |||||
ALsizei pos{0}; | |||||
const ALfloat gain{Gains[c]}; | |||||
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) | |||||
continue; | |||||
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; | |||||
} | |||||
} |
@ -1,263 +0,0 @@ | |||||
#include "config.h" | |||||
#include <xmmintrin.h> | |||||
#include <limits> | |||||
#include "AL/al.h" | |||||
#include "AL/alc.h" | |||||
#include "alMain.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>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, | |||||
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
MixHrtfParams *hrtfparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void MixHrtfBlend_<SSETag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, | |||||
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) | |||||
{ | |||||
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, | |||||
newparams, BufferSize); | |||||
} | |||||
template<> | |||||
void MixDirectHrtf_<SSETag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, | |||||
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, | |||||
const ALsizei NumChans, const ALsizei BufferSize) | |||||
{ | |||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans, | |||||
BufferSize); | |||||
} | |||||
template<> | |||||
void Mix_<SSETag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], | |||||
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, | |||||
const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(OutChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f}; | |||||
for(ALsizei c{0};c < OutChans;c++) | |||||
{ | |||||
ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])}; | |||||
ALsizei pos{0}; | |||||
ALfloat gain{CurrentGains[c]}; | |||||
const ALfloat diff{TargetGains[c] - gain}; | |||||
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[c]; | |||||
else | |||||
gain += step*step_count; | |||||
CurrentGains[c] = 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; | |||||
} | |||||
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>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], | |||||
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) | |||||
{ | |||||
ASSUME(InChans > 0); | |||||
ASSUME(BufferSize > 0); | |||||
for(ALsizei c{0};c < InChans;c++) | |||||
{ | |||||
const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])}; | |||||
const ALfloat gain{Gains[c]}; | |||||
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; | |||||
} | |||||
} |
@ -1,85 +0,0 @@ | |||||
/** | |||||
* OpenAL cross platform audio library | |||||
* Copyright (C) 2014 by Timothy Arceri <t_arceri@yahoo.com.au>. | |||||
* 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* UNUSED(state), | |||||
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; | |||||
} |
@ -1,878 +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 <cmath> | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <cctype> | |||||
#include <cassert> | |||||
#include <numeric> | |||||
#include <algorithm> | |||||
#include "AL/al.h" | |||||
#include "AL/alc.h" | |||||
#include "alMain.h" | |||||
#include "alcontext.h" | |||||
#include "alSource.h" | |||||
#include "alBuffer.h" | |||||
#include "alListener.h" | |||||
#include "alAuxEffectSlot.h" | |||||
#include "sample_cvt.h" | |||||
#include "alu.h" | |||||
#include "alconfig.h" | |||||
#include "ringbuffer.h" | |||||
#include "cpu_caps.h" | |||||
#include "mixer/defs.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() | |||||
{ | |||||
const char *str; | |||||
if(ConfigValueStr(nullptr, nullptr, "resampler", &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 { | |||||
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 *RESTRICT dst, const ALfloat *RESTRICT src, ALsizei numsamples, int type) | |||||
{ | |||||
switch(type) | |||||
{ | |||||
case AF_None: | |||||
lpfilter->passthru(numsamples); | |||||
hpfilter->passthru(numsamples); | |||||
break; | |||||
case AF_LowPass: | |||||
lpfilter->process(dst, src, numsamples); | |||||
hpfilter->passthru(numsamples); | |||||
return dst; | |||||
case AF_HighPass: | |||||
lpfilter->passthru(numsamples); | |||||
hpfilter->process(dst, src, numsamples); | |||||
return dst; | |||||
case AF_BandPass: | |||||
for(ALsizei i{0};i < numsamples;) | |||||
{ | |||||
ALfloat temp[256]; | |||||
ALsizei todo = mini(256, numsamples-i); | |||||
lpfilter->process(temp, src+i, todo); | |||||
hpfilter->process(dst+i, temp, todo); | |||||
i += todo; | |||||
} | |||||
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 void *src, ALint srcstep, | |||||
const ptrdiff_t samples) | |||||
{ | |||||
using SampleType = typename FmtTypeTraits<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 *RESTRICT dst, const ALvoid *RESTRICT 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, | |||||
ALfloat *SrcData, const ALfloat *const SrcDataEnd) | |||||
{ | |||||
/* 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) | |||||
{ | |||||
const ptrdiff_t SizeToDo{SrcDataEnd - SrcData}; | |||||
ASSUME(SizeToDo > 0); | |||||
BufferLoopItem = nullptr; | |||||
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t | |||||
{ | |||||
if(DataPosInt >= buffer->SampleLen) | |||||
return CompLen; | |||||
/* Load what's left to play from the buffer */ | |||||
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)}; | |||||
CompLen = std::max<ptrdiff_t>(CompLen, DataSize); | |||||
const ALbyte *Data{buffer->mData.data()}; | |||||
Data += (DataPosInt*NumChannels + chan)*SampleSize; | |||||
LoadSamples(SrcData, 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; | |||||
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, | |||||
load_buffer); | |||||
} | |||||
else | |||||
{ | |||||
const ptrdiff_t SizeToDo{std::min<ptrdiff_t>(SrcDataEnd-SrcData, LoopEnd-DataPosInt)}; | |||||
ASSUME(SizeToDo > 0); | |||||
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t | |||||
{ | |||||
if(DataPosInt >= buffer->SampleLen) | |||||
return CompLen; | |||||
/* Load what's left of this loop iteration */ | |||||
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)}; | |||||
CompLen = std::max<ptrdiff_t>(CompLen, DataSize); | |||||
const ALbyte *Data{buffer->mData.data()}; | |||||
Data += (DataPosInt*NumChannels + chan)*SampleSize; | |||||
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); | |||||
return CompLen; | |||||
}; | |||||
ASSUME(BufferListItem->num_buffers > 0); | |||||
auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; | |||||
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, | |||||
load_buffer); | |||||
const auto LoopSize = static_cast<ptrdiff_t>(LoopEnd - LoopStart); | |||||
while(SrcData != SrcDataEnd) | |||||
{ | |||||
const ptrdiff_t SizeToDo{std::min<ptrdiff_t>(SrcDataEnd-SrcData, LoopSize)}; | |||||
ASSUME(SizeToDo > 0); | |||||
auto load_buffer_loop = [LoopStart,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t | |||||
{ | |||||
if(LoopStart >= buffer->SampleLen) | |||||
return CompLen; | |||||
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, | |||||
buffer->SampleLen-LoopStart)}; | |||||
CompLen = std::max<ptrdiff_t>(CompLen, DataSize); | |||||
const ALbyte *Data{buffer->mData.data()}; | |||||
Data += (LoopStart*NumChannels + chan)*SampleSize; | |||||
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); | |||||
return CompLen; | |||||
}; | |||||
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, | |||||
load_buffer_loop); | |||||
} | |||||
} | |||||
return SrcData; | |||||
} | |||||
ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, | |||||
const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, | |||||
ALfloat *SrcData, const ALfloat *const SrcDataEnd) | |||||
{ | |||||
/* Crawl the buffer queue to fill in the temp buffer */ | |||||
while(BufferListItem && SrcData != SrcDataEnd) | |||||
{ | |||||
if(DataPosInt >= BufferListItem->max_samples) | |||||
{ | |||||
DataPosInt -= BufferListItem->max_samples; | |||||
BufferListItem = BufferListItem->next.load(std::memory_order_acquire); | |||||
if(!BufferListItem) BufferListItem = BufferLoopItem; | |||||
continue; | |||||
} | |||||
const ptrdiff_t SizeToDo{SrcDataEnd - SrcData}; | |||||
ASSUME(SizeToDo > 0); | |||||
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t | |||||
{ | |||||
if(!buffer) return CompLen; | |||||
if(DataPosInt >= buffer->SampleLen) | |||||
return CompLen; | |||||
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)}; | |||||
CompLen = std::max<ptrdiff_t>(CompLen, DataSize); | |||||
const ALbyte *Data{buffer->mData.data()}; | |||||
Data += (DataPosInt*NumChannels + chan)*SampleSize; | |||||
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); | |||||
return CompLen; | |||||
}; | |||||
ASSUME(BufferListItem->num_buffers > 0); | |||||
auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; | |||||
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0u}, | |||||
load_buffer); | |||||
if(SrcData == SrcDataEnd) | |||||
break; | |||||
DataPosInt = 0; | |||||
BufferListItem = BufferListItem->next.load(std::memory_order_acquire); | |||||
if(!BufferListItem) BufferListItem = BufferLoopItem; | |||||
} | |||||
return SrcData; | |||||
} | |||||
} // 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 IrSize{Device->mHrtf ? Device->mHrtf->irSize : 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++) | |||||
{ | |||||
DirectParams &parms = voice->mDirect.Params[chan]; | |||||
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; | |||||
auto set_current = [chan](ALvoice::SendData &send) -> void | |||||
{ | |||||
if(!send.Buffer) | |||||
return; | |||||
SendParams &parms = send.Params[chan]; | |||||
std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target), | |||||
std::begin(parms.Gains.Current)); | |||||
}; | |||||
std::for_each(voice->mSend.begin(), voice->mSend.end(), set_current); | |||||
} | |||||
} | |||||
else if((voice->mFlags&VOICE_HAS_HRTF)) | |||||
{ | |||||
for(ALsizei chan{0};chan < NumChannels;chan++) | |||||
{ | |||||
DirectParams &parms = voice->mDirect.Params[chan]; | |||||
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<ALsizei>( | |||||
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++) | |||||
{ | |||||
auto &SrcData = Device->SourceData; | |||||
/* Load the previous samples into the source data first, and clear the rest. */ | |||||
auto srciter = std::copy_n(voice->mResampleData[chan].mPrevSamples.begin(), | |||||
MAX_RESAMPLE_PADDING, std::begin(SrcData)); | |||||
std::fill(srciter, std::end(SrcData), 0.0f); | |||||
auto srcdata_end = std::begin(SrcData) + SrcBufferSize; | |||||
if(UNLIKELY(!BufferListItem)) | |||||
srciter = std::copy( | |||||
voice->mResampleData[chan].mPrevSamples.begin()+MAX_RESAMPLE_PADDING, | |||||
voice->mResampleData[chan].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], | |||||
voice->mResampleData[chan].mPrevSamples.size(), | |||||
voice->mResampleData[chan].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{voice->mResampleData[chan].mAmbiScale}; | |||||
/* Beware the evil const_cast. It's safe since it's pointing to | |||||
* either SrcData or Device->ResampledData (both non-const), | |||||
* but the resample method takes its input as const float* and | |||||
* may return it without copying to output, making it currently | |||||
* unavoidable. | |||||
*/ | |||||
voice->mResampleData[chan].mAmbiSplitter.applyHfScale( | |||||
const_cast<ALfloat*>(ResampledData), hfscale, DstBufferSize); | |||||
} | |||||
/* Now filter and mix to the appropriate outputs. */ | |||||
{ | |||||
DirectParams &parms = voice->mDirect.Params[chan]; | |||||
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); | |||||
} | |||||
MixHrtfParams 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); | |||||
} | |||||
MixHrtfParams 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}; | |||||
MixSamples(samples, voice->mDirect.ChannelsPerOrder[0], | |||||
voice->mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, | |||||
OutPos, DstBufferSize); | |||||
ALfloat (&nfcsamples)[BUFFERSIZE] = Device->NfcSampleData; | |||||
ALsizei chanoffset{voice->mDirect.ChannelsPerOrder[0]}; | |||||
using FilterProc = void (NfcFilter::*)(float*,const float*,int); | |||||
auto apply_nfc = [voice,&parms,samples,TargetGains,DstBufferSize,Counter,OutPos,&chanoffset,&nfcsamples](FilterProc process, ALsizei order) -> void | |||||
{ | |||||
if(voice->mDirect.ChannelsPerOrder[order] < 1) | |||||
return; | |||||
(parms.NFCtrlFilter.*process)(nfcsamples, samples, DstBufferSize); | |||||
MixSamples(nfcsamples, voice->mDirect.ChannelsPerOrder[order], | |||||
voice->mDirect.Buffer+chanoffset, parms.Gains.Current+chanoffset, | |||||
TargetGains+chanoffset, Counter, OutPos, DstBufferSize); | |||||
chanoffset += voice->mDirect.ChannelsPerOrder[order]; | |||||
}; | |||||
apply_nfc(&NfcFilter::process1, 1); | |||||
apply_nfc(&NfcFilter::process2, 2); | |||||
apply_nfc(&NfcFilter::process3, 3); | |||||
} | |||||
else | |||||
{ | |||||
const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? | |||||
SilentTarget : parms.Gains.Target}; | |||||
MixSamples(samples, voice->mDirect.Channels, voice->mDirect.Buffer, | |||||
parms.Gains.Current, TargetGains, Counter, OutPos, DstBufferSize); | |||||
} | |||||
} | |||||
ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData; | |||||
auto mix_send = [vstate,Counter,OutPos,DstBufferSize,chan,ResampledData,&FilterBuf](ALvoice::SendData &send) -> void | |||||
{ | |||||
if(!send.Buffer) | |||||
return; | |||||
SendParams &parms = send.Params[chan]; | |||||
const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, | |||||
FilterBuf, ResampledData, DstBufferSize, send.FilterType)}; | |||||
const ALfloat *TargetGains{UNLIKELY(vstate==ALvoice::Stopping) ? SilentTarget : | |||||
parms.Gains.Target}; | |||||
MixSamples(samples, send.Channels, send.Buffer, parms.Gains.Current, | |||||
TargetGains, Counter, OutPos, DstBufferSize); | |||||
}; | |||||
std::for_each(voice->mSend.begin(), voice->mSend.end(), mix_send); | |||||
} | |||||
/* 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); | |||||
} | |||||
} |
@ -1,126 +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(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo) | |||||
{ | |||||
alignas(16) ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES]; | |||||
alignas(16) ALfloat temp[MAX_UPDATE_SAMPLES]; | |||||
ASSUME(SamplesToDo > 0); | |||||
for(ALsizei base{0};base < SamplesToDo;) | |||||
{ | |||||
ALsizei todo = mini(SamplesToDo - base, MAX_UPDATE_SAMPLES); | |||||
ASSUME(todo > 0); | |||||
/* D = 0.6554516*Y */ | |||||
const ALfloat *RESTRICT input{al::assume_aligned<16>(InSamples[2]+base)}; | |||||
for(ALsizei i{0};i < todo;i++) | |||||
temp[i] = 0.6554516f*input[i]; | |||||
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) */ | |||||
const ALfloat *RESTRICT input0{al::assume_aligned<16>(InSamples[0]+base)}; | |||||
const ALfloat *RESTRICT input1{al::assume_aligned<16>(InSamples[1]+base)}; | |||||
for(ALsizei i{0};i < todo;i++) | |||||
temp[i] = -0.3420201f*input0[i] + 0.5098604f*input1[i]; | |||||
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 */ | |||||
for(ALsizei i{0};i < todo;i++) | |||||
temp[i] = 0.9396926f*input0[i] + 0.1855740f*input1[i]; | |||||
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+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+base); | |||||
for(ALsizei i{0};i < todo;i++) | |||||
right[i] += (S[i] - D[i]) * 0.5f; | |||||
base += todo; | |||||
} | |||||
} |
@ -1,53 +0,0 @@ | |||||
#ifndef UHJFILTER_H | |||||
#define UHJFILTER_H | |||||
#include "AL/al.h" | |||||
#include "alMain.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(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo); | |||||
DEF_NEWDEL(Uhj2Encoder) | |||||
}; | |||||
#endif /* UHJFILTER_H */ |
@ -0,0 +1,31 @@ | |||||
Portions of this software are licensed under the BSD 3-Clause license. | |||||
Copyright (c) 2015, Archontis Politis | |||||
Copyright (c) 2019, Anis A. Hireche | |||||
Copyright (c) 2019, Christopher Robinson | |||||
All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions are met: | |||||
* Redistributions of source code must retain the above copyright notice, this | |||||
list of conditions and the following disclaimer. | |||||
* Redistributions in binary form must reproduce the above copyright notice, | |||||
this list of conditions and the following disclaimer in the documentation | |||||
and/or other materials provided with the distribution. | |||||
* Neither the name of Spherical-Harmonic-Transform nor the names of its | |||||
contributors may be used to endorse or promote products derived from | |||||
this software without specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -1,100 +0,0 @@ | |||||
#ifndef _AL_AUXEFFECTSLOT_H_ | |||||
#define _AL_AUXEFFECTSLOT_H_ | |||||
#include <array> | |||||
#include "alMain.h" | |||||
#include "alEffect.h" | |||||
#include "ambidefs.h" | |||||
#include "effects/base.h" | |||||
#include "almalloc.h" | |||||
#include "atomic.h" | |||||
struct ALeffectslot; | |||||
using ALeffectslotArray = al::FlexArray<ALeffectslot*>; | |||||
struct ALeffectslotProps { | |||||
ALfloat Gain; | |||||
ALboolean AuxSendAuto; | |||||
ALeffectslot *Target; | |||||
ALenum Type; | |||||
EffectProps Props; | |||||
EffectState *State; | |||||
std::atomic<ALeffectslotProps*> next; | |||||
}; | |||||
struct ALeffectslot { | |||||
ALfloat Gain{1.0f}; | |||||
ALboolean AuxSendAuto{AL_TRUE}; | |||||
ALeffectslot *Target{nullptr}; | |||||
struct { | |||||
ALenum Type{AL_EFFECT_NULL}; | |||||
EffectProps Props{}; | |||||
EffectState *State{nullptr}; | |||||
} Effect; | |||||
std::atomic_flag PropsClean; | |||||
RefCount ref{0u}; | |||||
std::atomic<ALeffectslotProps*> Update{nullptr}; | |||||
struct { | |||||
ALfloat Gain{1.0f}; | |||||
ALboolean AuxSendAuto{AL_TRUE}; | |||||
ALeffectslot *Target{nullptr}; | |||||
ALenum EffectType{AL_EFFECT_NULL}; | |||||
EffectProps mEffectProps{}; | |||||
EffectState *mEffectState{nullptr}; | |||||
ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ | |||||
ALfloat DecayTime{0.0f}; | |||||
ALfloat DecayLFRatio{0.0f}; | |||||
ALfloat DecayHFRatio{0.0f}; | |||||
ALboolean DecayHFLimit{AL_FALSE}; | |||||
ALfloat AirAbsorptionGainHF{1.0f}; | |||||
} Params; | |||||
/* Self ID */ | |||||
ALuint id{}; | |||||
/* Mixing buffer used by the Wet mix. */ | |||||
al::vector<std::array<ALfloat,BUFFERSIZE>,16> MixBuffer; | |||||
/* Wet buffer configuration is ACN channel order with N3D scaling. | |||||
* Consequently, effects that only want to work with mono input can use | |||||
* channel 0 by itself. Effects that want multichannel can process the | |||||
* ambisonics signal and make a B-Format source pan. | |||||
*/ | |||||
MixParams Wet; | |||||
ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); } | |||||
ALeffectslot(const ALeffectslot&) = delete; | |||||
ALeffectslot& operator=(const ALeffectslot&) = delete; | |||||
~ALeffectslot(); | |||||
static ALeffectslotArray *CreatePtrArray(size_t count) noexcept; | |||||
DEF_PLACE_NEWDEL() | |||||
}; | |||||
ALenum InitEffectSlot(ALeffectslot *slot); | |||||
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context); | |||||
void UpdateAllEffectSlotProps(ALCcontext *context); | |||||
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect); | |||||
#endif |