@ -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*/ | |||
winbuild/ | |||
win64build/ | |||
openal-soft.kdev4 | |||
.kdev4/ | |||
winbuild | |||
win64build | |||
## kdevelop | |||
*.kdev4 | |||
## qt-creator | |||
CMakeLists.txt.user* |
@ -1,27 +1,18 @@ | |||
#ifndef 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 */ |
@ -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 <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; } | |||
ClockLatency BackendBase::getClockLatency() | |||
{ | |||
ClockLatency ret; | |||
ALuint refcount; | |||
uint refcount; | |||
do { | |||
while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1)) | |||
std::this_thread::yield(); | |||
refcount = mDevice->waitForMix(); | |||
ret.ClockTime = GetDeviceClockTime(mDevice); | |||
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 | |||
* any given time during playback. Without a more accurate measurement from | |||
* the output, this is an okay approximation. | |||
*/ | |||
ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)}; | |||
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, | |||
std::chrono::seconds::zero()); | |||
ret.Latency /= mDevice->Frequency; | |||
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 |