mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-16 09:04:38 +00:00
* BugFix: Correct convexDecomp compilation by setting the LINUX flag when necessary.
* BugFix: Update OpenAL to correct a compilation error on Linux.
This commit is contained in:
parent
e071f1d901
commit
7380161054
234 changed files with 30864 additions and 7523 deletions
|
|
@ -179,6 +179,9 @@ al::optional<std::string> load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1],
|
|||
|
||||
} // namespace
|
||||
|
||||
AmbDecConf::~AmbDecConf() = default;
|
||||
|
||||
|
||||
al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
||||
{
|
||||
al::ifstream f{fname};
|
||||
|
|
@ -198,7 +201,7 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
return al::make_optional("Malformed line: "+buffer);
|
||||
|
||||
if(command == "/description")
|
||||
istr >> Description;
|
||||
readline(istr, Description);
|
||||
else if(command == "/version")
|
||||
{
|
||||
istr >> Version;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ struct AmbDecConf {
|
|||
float HFOrderGain[MaxAmbiOrder+1]{};
|
||||
CoeffArray *HFMatrix;
|
||||
|
||||
~AmbDecConf();
|
||||
|
||||
al::optional<std::string> load(const char *fname) noexcept;
|
||||
};
|
||||
|
||||
|
|
|
|||
44
Engine/lib/openal-soft/core/ambidefs.cpp
Normal file
44
Engine/lib/openal-soft/core/ambidefs.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "ambidefs.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale{{
|
||||
1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale2O{{
|
||||
7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale3O{{
|
||||
5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
|
||||
inline auto& GetDecoderHFScales(uint order) noexcept
|
||||
{
|
||||
if(order >= 3) return Ambi3DDecoderHFScale3O;
|
||||
if(order == 2) return Ambi3DDecoderHFScale2O;
|
||||
return Ambi3DDecoderHFScale;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto AmbiScale::GetHFOrderScales(const uint in_order, const uint out_order) noexcept
|
||||
-> std::array<float,MaxAmbiOrder+1>
|
||||
{
|
||||
std::array<float,MaxAmbiOrder+1> ret{};
|
||||
|
||||
assert(out_order >= in_order);
|
||||
|
||||
const auto &target = GetDecoderHFScales(out_order);
|
||||
const auto &input = GetDecoderHFScales(in_order);
|
||||
|
||||
for(size_t i{0};i < in_order+1;++i)
|
||||
ret[i] = input[i] / target[i];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -97,6 +97,22 @@ struct AmbiScale {
|
|||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& FromUHJ() noexcept
|
||||
{
|
||||
static constexpr const std::array<float,MaxAmbiChannels> ret{{
|
||||
1.000000000f, /* ACN 0 (W), sqrt(1) */
|
||||
1.224744871f, /* ACN 1 (Y), sqrt(3/2) */
|
||||
1.224744871f, /* ACN 2 (Z), sqrt(3/2) */
|
||||
1.224744871f, /* ACN 3 (X), sqrt(3/2) */
|
||||
/* Higher orders not relevant for UHJ. */
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
|
||||
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint in_order,
|
||||
const uint out_order) noexcept;
|
||||
};
|
||||
|
||||
struct AmbiIndex {
|
||||
|
|
|
|||
55
Engine/lib/openal-soft/core/async_event.h
Normal file
55
Engine/lib/openal-soft/core/async_event.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef CORE_EVENT_H
|
||||
#define CORE_EVENT_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct EffectState;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
struct AsyncEvent {
|
||||
enum : uint {
|
||||
/* End event thread processing. */
|
||||
KillThread = 0,
|
||||
|
||||
/* User event types. */
|
||||
SourceStateChange = 1<<0,
|
||||
BufferCompleted = 1<<1,
|
||||
Disconnected = 1<<2,
|
||||
|
||||
/* Internal events. */
|
||||
ReleaseEffectState = 65536,
|
||||
};
|
||||
|
||||
enum class SrcState {
|
||||
Reset,
|
||||
Stop,
|
||||
Play,
|
||||
Pause
|
||||
};
|
||||
|
||||
uint EnumType{0u};
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
uint id;
|
||||
SrcState state;
|
||||
} srcstate;
|
||||
struct {
|
||||
uint id;
|
||||
uint count;
|
||||
} bufcomp;
|
||||
struct {
|
||||
char msg[244];
|
||||
} disconnect;
|
||||
EffectState *mEffectState;
|
||||
} u{};
|
||||
|
||||
AsyncEvent() noexcept = default;
|
||||
constexpr AsyncEvent(uint type) noexcept : EnumType{type} { }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
#endif
|
||||
192
Engine/lib/openal-soft/core/bformatdec.cpp
Normal file
192
Engine/lib/openal-soft/core/bformatdec.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "front_stablizer.h"
|
||||
#include "mixer.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
BFormatDec::BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
|
||||
const al::span<const ChannelDec> coeffslf, const float xover_f0norm,
|
||||
std::unique_ptr<FrontStablizer> stablizer)
|
||||
: mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans}
|
||||
{
|
||||
if(!mDualBand)
|
||||
{
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Single};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelDec[0].mXOver.init(xover_f0norm);
|
||||
for(size_t j{1};j < mChannelDec.size();++j)
|
||||
mChannelDec[j].mXOver = mChannelDec[0].mXOver;
|
||||
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
|
||||
outcoeffs = mChannelDec[j].mGains.Dual[sLFBand];
|
||||
for(const ChannelDec &incoeffs : coeffslf)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
if(mDualBand)
|
||||
{
|
||||
const al::span<float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
|
||||
const al::span<float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(),
|
||||
lfSamples.data());
|
||||
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand],
|
||||
chandec.mGains.Dual[sHFBand], 0, 0);
|
||||
MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand],
|
||||
chandec.mGains.Dual[sLFBand], 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
|
||||
chandec.mGains.Single, 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
|
||||
const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* Move the existing direct L/R signal out so it doesn't get processed by
|
||||
* the stablizer. Add a delay to it so it stays aligned with the stablizer
|
||||
* delay.
|
||||
*/
|
||||
float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())};
|
||||
float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())};
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
{
|
||||
mid[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
}
|
||||
std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f);
|
||||
std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f);
|
||||
|
||||
/* Decode the B-Format input to OutBuffer. */
|
||||
process(OutBuffer, InSamples, SamplesToDo);
|
||||
|
||||
/* Apply a delay to all channels, except the front-left and front-right, so
|
||||
* they maintain correct timing.
|
||||
*/
|
||||
const size_t NumChannels{OutBuffer.size()};
|
||||
for(size_t i{0u};i < NumChannels;i++)
|
||||
{
|
||||
if(i == lidx || i == ridx)
|
||||
continue;
|
||||
|
||||
auto &DelayBuf = mStablizer->DelayBuf[i];
|
||||
auto buffer_end = OutBuffer[i].begin() + SamplesToDo;
|
||||
if LIKELY(SamplesToDo >= FrontStablizer::DelayLength)
|
||||
{
|
||||
auto delay_end = std::rotate(OutBuffer[i].begin(),
|
||||
buffer_end - FrontStablizer::DelayLength, buffer_end);
|
||||
std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end,
|
||||
DelayBuf.begin());
|
||||
std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end());
|
||||
}
|
||||
}
|
||||
|
||||
/* Include the side signal for what was just decoded. */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
|
||||
/* Combine the delayed mid signal with the decoded mid signal. */
|
||||
float *tmpbuf{mStablizer->TempBuf.data()};
|
||||
auto tmpiter = std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpbuf);
|
||||
for(size_t i{0};i < SamplesToDo;++i,++tmpiter)
|
||||
*tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
/* Save the newest samples for next time. */
|
||||
std::copy_n(tmpbuf+SamplesToDo, mStablizer->MidDelay.size(), mStablizer->MidDelay.begin());
|
||||
|
||||
/* Apply an all-pass on the signal in reverse. The future samples are
|
||||
* included with the all-pass to reduce the error in the output samples
|
||||
* (the smaller the delay, the more error is introduced).
|
||||
*/
|
||||
mStablizer->MidFilter.applyAllpassRev({tmpbuf, SamplesToDo+FrontStablizer::DelayLength});
|
||||
|
||||
/* Now apply the band-splitter, combining its phase shift with the reversed
|
||||
* phase shift, restoring the original phase on the split signal.
|
||||
*/
|
||||
mStablizer->MidFilter.process({tmpbuf, SamplesToDo}, mStablizer->MidHF.data(),
|
||||
mStablizer->MidLF.data());
|
||||
|
||||
/* This pans the separate low- and high-frequency signals between being on
|
||||
* the center channel and the left+right channels. The low-frequency signal
|
||||
* is panned 1/3rd toward center and the high-frequency signal is panned
|
||||
* 1/4th toward center. These values can be tweaked.
|
||||
*/
|
||||
const float cos_lf{std::cos(1.0f/3.0f * (al::numbers::pi_v<float>*0.5f))};
|
||||
const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v<float>*0.5f))};
|
||||
const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v<float>*0.5f))};
|
||||
const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v<float>*0.5f))};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
{
|
||||
const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]};
|
||||
const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf};
|
||||
const float s{side[i]};
|
||||
|
||||
/* The generated center channel signal adds to the existing signal,
|
||||
* while the modified left and right channels replace.
|
||||
*/
|
||||
OutBuffer[lidx][i] = (m + s) * 0.5f;
|
||||
OutBuffer[ridx][i] = (m - s) * 0.5f;
|
||||
OutBuffer[cidx][i] += c * 0.5f;
|
||||
}
|
||||
/* Move the delayed mid/side samples to the front for next time. */
|
||||
auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo;
|
||||
std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin());
|
||||
auto side_end = mStablizer->Side.cbegin() + SamplesToDo;
|
||||
std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin());
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<BFormatDec> BFormatDec::Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
const float xover_f0norm, std::unique_ptr<FrontStablizer> stablizer)
|
||||
{
|
||||
return std::make_unique<BFormatDec>(inchans, coeffs, coeffslf, xover_f0norm,
|
||||
std::move(stablizer));
|
||||
}
|
||||
71
Engine/lib/openal-soft/core/bformatdec.h
Normal file
71
Engine/lib/openal-soft/core/bformatdec.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef CORE_BFORMATDEC_H
|
||||
#define CORE_BFORMATDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct FrontStablizer;
|
||||
|
||||
|
||||
using ChannelDec = std::array<float,MaxAmbiChannels>;
|
||||
|
||||
class BFormatDec {
|
||||
static constexpr size_t sHFBand{0};
|
||||
static constexpr size_t sLFBand{1};
|
||||
static constexpr size_t sNumBands{2};
|
||||
|
||||
struct ChannelDecoder {
|
||||
union MatrixU {
|
||||
float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
|
||||
float Single[MAX_OUTPUT_CHANNELS];
|
||||
} mGains{};
|
||||
|
||||
/* NOTE: BandSplitter filter is unused with single-band decoding. */
|
||||
BandSplitter mXOver;
|
||||
};
|
||||
|
||||
alignas(16) std::array<FloatBufferLine,2> mSamples;
|
||||
|
||||
const std::unique_ptr<FrontStablizer> mStablizer;
|
||||
const bool mDualBand{false};
|
||||
|
||||
/* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather
|
||||
* small and only a few are needed (3, 4, 5, 7, typically). But that can
|
||||
* only be used in a standard layout struct, and a std::unique_ptr member
|
||||
* (mStablizer) causes GCC and Clang to warn it's not.
|
||||
*/
|
||||
al::vector<ChannelDecoder> mChannelDec;
|
||||
|
||||
public:
|
||||
BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
|
||||
const al::span<const ChannelDec> coeffslf, const float xover_f0norm,
|
||||
std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
bool hasStablizer() const noexcept { return mStablizer != nullptr; }
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels. */
|
||||
void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels with stablization. */
|
||||
void processStablize(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
static std::unique_ptr<BFormatDec> Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
const float xover_f0norm, std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
DEF_NEWDEL(BFormatDec)
|
||||
};
|
||||
|
||||
#endif /* CORE_BFORMATDEC_H */
|
||||
|
|
@ -27,8 +27,8 @@
|
|||
#include <cmath>
|
||||
#include <iterator>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "bs2b.h"
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
/* Set up all data. */
|
||||
|
|
@ -91,11 +91,11 @@ static void init(struct bs2b *bs2b)
|
|||
* $d = 1 / 2 / pi / $fc;
|
||||
* $x = exp(-1 / $d);
|
||||
*/
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate));
|
||||
x = std::exp(-al::numbers::pi_v<float>*2.0f*Fc_lo/static_cast<float>(bs2b->srate));
|
||||
bs2b->b1_lo = x;
|
||||
bs2b->a0_lo = G_lo * (1.0f - x) * g;
|
||||
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate));
|
||||
x = std::exp(-al::numbers::pi_v<float>*2.0f*Fc_hi/static_cast<float>(bs2b->srate));
|
||||
bs2b->b1_hi = x;
|
||||
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
|
||||
bs2b->a1_hi = -x * g;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,4 @@ constexpr unsigned int BSincScaleCount{1 << BSincScaleBits};
|
|||
constexpr unsigned int BSincPhaseBits{5};
|
||||
constexpr unsigned int BSincPhaseCount{1 << BSincPhaseBits};
|
||||
|
||||
/* The maximum number of sample points for the bsinc filters. The max points
|
||||
* includes the doubling for downsampling, so the maximum number of base sample
|
||||
* points is 24, which is 23rd order.
|
||||
*/
|
||||
constexpr unsigned int BSincPointsMax{48};
|
||||
|
||||
#endif /* CORE_BSINC_DEFS_H */
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "math_defs.h"
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
|
@ -24,9 +25,10 @@ using uint = unsigned int;
|
|||
*/
|
||||
constexpr double Sinc(const double x)
|
||||
{
|
||||
if(!(x > 1e-15 || x < -1e-15))
|
||||
constexpr double epsilon{std::numeric_limits<double>::epsilon()};
|
||||
if(!(x > epsilon || x < -epsilon))
|
||||
return 1.0;
|
||||
return std::sin(al::MathDefs<double>::Pi()*x) / (al::MathDefs<double>::Pi()*x);
|
||||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
|
|
@ -35,7 +37,7 @@ constexpr double Sinc(const double x)
|
|||
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
|
||||
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
|
||||
*/
|
||||
constexpr double BesselI_0(const double x)
|
||||
constexpr double BesselI_0(const double x) noexcept
|
||||
{
|
||||
/* Start at k=1 since k=0 is trivial. */
|
||||
const double x2{x / 2.0};
|
||||
|
|
@ -82,12 +84,12 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_
|
|||
/* Calculates the (normalized frequency) transition width of the Kaiser window.
|
||||
* Rejection is in dB.
|
||||
*/
|
||||
constexpr double CalcKaiserWidth(const double rejection, const uint order)
|
||||
constexpr double CalcKaiserWidth(const double rejection, const uint order) noexcept
|
||||
{
|
||||
if(rejection > 21.19)
|
||||
return (rejection - 7.95) / (order * 2.285 * al::MathDefs<double>::Tau());
|
||||
return (rejection - 7.95) / (2.285 * al::numbers::pi*2.0 * order);
|
||||
/* This enforces a minimum rejection of just above 21.18dB */
|
||||
return 5.79 / (order * al::MathDefs<double>::Tau());
|
||||
return 5.79 / (al::numbers::pi*2.0 * order);
|
||||
}
|
||||
|
||||
/* Calculates the beta value of the Kaiser window. Rejection is in dB. */
|
||||
|
|
@ -122,7 +124,7 @@ struct BSincHeader {
|
|||
uint num_points{Order+1};
|
||||
for(uint si{0};si < BSincScaleCount;++si)
|
||||
{
|
||||
const double scale{scaleBase + (scaleRange * si / (BSincScaleCount-1))};
|
||||
const double scale{scaleBase + (scaleRange * (si+1) / BSincScaleCount)};
|
||||
const uint a_{std::min(static_cast<uint>(num_points / 2.0 / scale), num_points)};
|
||||
const uint m{2 * a_};
|
||||
|
||||
|
|
@ -144,21 +146,33 @@ constexpr BSincHeader bsinc24_hdr{60, 23};
|
|||
* namespace while also being used as non-type template parameters.
|
||||
*/
|
||||
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6
|
||||
|
||||
/* The number of sample points is double the a value (rounded up to a multiple
|
||||
* of 4), and scale index 0 includes the doubling for downsampling. bsinc24 is
|
||||
* currently the highest quality filter, and will use the most sample points.
|
||||
*/
|
||||
constexpr uint BSincPointsMax{(bsinc24_hdr.a[0]*2 + 3) & ~3u};
|
||||
static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small");
|
||||
|
||||
template<size_t total_size>
|
||||
struct BSincFilterArray {
|
||||
alignas(16) std::array<float, total_size> mTable;
|
||||
const BSincHeader &hdr;
|
||||
|
||||
BSincFilterArray(const BSincHeader &hdr)
|
||||
BSincFilterArray(const BSincHeader &hdr_) : hdr{hdr_}
|
||||
{
|
||||
#else
|
||||
template<const BSincHeader &hdr>
|
||||
struct BSincFilterArray {
|
||||
alignas(16) std::array<float, hdr.total_size> mTable;
|
||||
alignas(16) std::array<float, hdr.total_size> mTable{};
|
||||
|
||||
BSincFilterArray()
|
||||
#endif
|
||||
{
|
||||
using filter_type = double[][BSincPhaseCount+1][BSincPointsMax];
|
||||
auto filter = std::make_unique<filter_type>(BSincScaleCount);
|
||||
constexpr uint BSincPointsMax{(hdr.a[0]*2 + 3) & ~3u};
|
||||
static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small");
|
||||
#endif
|
||||
using filter_type = double[BSincPhaseCount+1][BSincPointsMax];
|
||||
auto filter = std::make_unique<filter_type[]>(BSincScaleCount);
|
||||
|
||||
/* Calculate the Kaiser-windowed Sinc filter coefficients for each
|
||||
* scale and phase index.
|
||||
|
|
@ -167,38 +181,38 @@ struct BSincFilterArray {
|
|||
{
|
||||
const uint m{hdr.a[si] * 2};
|
||||
const size_t o{(BSincPointsMax-m) / 2};
|
||||
const double scale{hdr.scaleBase + (hdr.scaleRange * si / (BSincScaleCount-1))};
|
||||
const double cutoff{scale - (hdr.scaleBase * std::max(0.5, scale) * 2.0)};
|
||||
const double scale{hdr.scaleBase + (hdr.scaleRange * (si+1) / BSincScaleCount)};
|
||||
const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))};
|
||||
const auto a = static_cast<double>(hdr.a[si]);
|
||||
const double l{a - 1.0};
|
||||
const double l{a - 1.0/BSincPhaseCount};
|
||||
|
||||
/* Do one extra phase index so that the phase delta has a proper
|
||||
* target for its last index.
|
||||
*/
|
||||
for(uint pi{0};pi <= BSincPhaseCount;++pi)
|
||||
{
|
||||
const double phase{l + (pi/double{BSincPhaseCount})};
|
||||
const double phase{std::floor(l) + (pi/double{BSincPhaseCount})};
|
||||
|
||||
for(uint i{0};i < m;++i)
|
||||
{
|
||||
const double x{i - phase};
|
||||
filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, hdr.besseli_0_beta) * cutoff *
|
||||
filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, hdr.besseli_0_beta) * cutoff *
|
||||
Sinc(cutoff*x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t idx{0};
|
||||
for(size_t si{0};si < BSincScaleCount-1;++si)
|
||||
for(size_t si{0};si < BSincScaleCount;++si)
|
||||
{
|
||||
const size_t m{((hdr.a[si]*2) + 3) & ~3u};
|
||||
const size_t o{(BSincPointsMax-m) / 2};
|
||||
|
||||
/* Write out each phase index's filter and phase delta for this
|
||||
* quality scale.
|
||||
*/
|
||||
for(size_t pi{0};pi < BSincPhaseCount;++pi)
|
||||
{
|
||||
/* Write out the filter. Also calculate and write out the phase
|
||||
* and scale deltas.
|
||||
*/
|
||||
for(size_t i{0};i < m;++i)
|
||||
mTable[idx++] = static_cast<float>(filter[si][pi][o+i]);
|
||||
|
||||
|
|
@ -210,11 +224,22 @@ struct BSincFilterArray {
|
|||
const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]};
|
||||
mTable[idx++] = static_cast<float>(phDelta);
|
||||
}
|
||||
|
||||
}
|
||||
/* Calculate and write out each phase index's filter quality scale
|
||||
* deltas. The last scale index doesn't have any scale or scale-
|
||||
* phase deltas.
|
||||
*/
|
||||
if(si == BSincScaleCount-1)
|
||||
{
|
||||
for(size_t i{0};i < BSincPhaseCount*m*2;++i)
|
||||
mTable[idx++] = 0.0f;
|
||||
}
|
||||
else for(size_t pi{0};pi < BSincPhaseCount;++pi)
|
||||
{
|
||||
/* Linear interpolation between scales is also simplified.
|
||||
*
|
||||
* Given a difference in points between scales, the destination
|
||||
* points will be 0, thus: x = a + f (-a)
|
||||
* Given a difference in the number of points between scales,
|
||||
* the destination points will be 0, thus: x = a + f (-a)
|
||||
*/
|
||||
for(size_t i{0};i < m;++i)
|
||||
{
|
||||
|
|
@ -233,31 +258,11 @@ struct BSincFilterArray {
|
|||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
/* The last scale index doesn't have any scale or scale-phase
|
||||
* deltas.
|
||||
*/
|
||||
constexpr size_t si{BSincScaleCount-1};
|
||||
const size_t m{((hdr.a[si]*2) + 3) & ~3u};
|
||||
const size_t o{(BSincPointsMax-m) / 2};
|
||||
|
||||
for(size_t pi{0};pi < BSincPhaseCount;++pi)
|
||||
{
|
||||
for(size_t i{0};i < m;++i)
|
||||
mTable[idx++] = static_cast<float>(filter[si][pi][o+i]);
|
||||
for(size_t i{0};i < m;++i)
|
||||
{
|
||||
const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]};
|
||||
mTable[idx++] = static_cast<float>(phDelta);
|
||||
}
|
||||
for(size_t i{0};i < m;++i)
|
||||
mTable[idx++] = 0.0f;
|
||||
for(size_t i{0};i < m;++i)
|
||||
mTable[idx++] = 0.0f;
|
||||
}
|
||||
}
|
||||
assert(idx == hdr.total_size);
|
||||
}
|
||||
|
||||
constexpr const BSincHeader &getHeader() const noexcept { return hdr; }
|
||||
constexpr const float *getTable() const noexcept { return &mTable.front(); }
|
||||
};
|
||||
|
||||
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6
|
||||
|
|
@ -268,9 +273,11 @@ const BSincFilterArray<bsinc12_hdr> bsinc12_filter{};
|
|||
const BSincFilterArray<bsinc24_hdr> bsinc24_filter{};
|
||||
#endif
|
||||
|
||||
constexpr BSincTable GenerateBSincTable(const BSincHeader &hdr, const float *tab)
|
||||
template<typename T>
|
||||
constexpr BSincTable GenerateBSincTable(const T &filter)
|
||||
{
|
||||
BSincTable ret{};
|
||||
const BSincHeader &hdr = filter.getHeader();
|
||||
ret.scaleBase = static_cast<float>(hdr.scaleBase);
|
||||
ret.scaleRange = static_cast<float>(1.0 / hdr.scaleRange);
|
||||
for(size_t i{0};i < BSincScaleCount;++i)
|
||||
|
|
@ -278,11 +285,11 @@ constexpr BSincTable GenerateBSincTable(const BSincHeader &hdr, const float *tab
|
|||
ret.filterOffset[0] = 0;
|
||||
for(size_t i{1};i < BSincScaleCount;++i)
|
||||
ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount;
|
||||
ret.Tab = tab;
|
||||
ret.Tab = filter.getTable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const BSincTable bsinc12{GenerateBSincTable(bsinc12_hdr, &bsinc12_filter.mTable.front())};
|
||||
const BSincTable bsinc24{GenerateBSincTable(bsinc24_hdr, &bsinc24_filter.mTable.front())};
|
||||
const BSincTable bsinc12{GenerateBSincTable(bsinc12_filter)};
|
||||
const BSincTable bsinc24{GenerateBSincTable(bsinc24_filter)};
|
||||
|
|
|
|||
42
Engine/lib/openal-soft/core/buffer_storage.cpp
Normal file
42
Engine/lib/openal-soft/core/buffer_storage.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "buffer_storage.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return sizeof(uint8_t);
|
||||
case FmtShort: return sizeof(int16_t);
|
||||
case FmtFloat: return sizeof(float);
|
||||
case FmtDouble: return sizeof(double);
|
||||
case FmtMulaw: return sizeof(uint8_t);
|
||||
case FmtAlaw: return sizeof(uint8_t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case FmtMono: return 1;
|
||||
case FmtStereo: return 2;
|
||||
case FmtRear: return 2;
|
||||
case FmtQuad: return 4;
|
||||
case FmtX51: return 6;
|
||||
case FmtX61: return 7;
|
||||
case FmtX71: return 8;
|
||||
case FmtBFormat2D: return (ambiorder*2) + 1;
|
||||
case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
|
||||
case FmtUHJ2: return 2;
|
||||
case FmtUHJ3: return 3;
|
||||
case FmtUHJ4: return 4;
|
||||
case FmtSuperStereo: return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
99
Engine/lib/openal-soft/core/buffer_storage.h
Normal file
99
Engine/lib/openal-soft/core/buffer_storage.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#ifndef CORE_BUFFER_STORAGE_H
|
||||
#define CORE_BUFFER_STORAGE_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "ambidefs.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType : unsigned char {
|
||||
FmtUByte,
|
||||
FmtShort,
|
||||
FmtFloat,
|
||||
FmtDouble,
|
||||
FmtMulaw,
|
||||
FmtAlaw,
|
||||
};
|
||||
enum FmtChannels : unsigned char {
|
||||
FmtMono,
|
||||
FmtStereo,
|
||||
FmtRear,
|
||||
FmtQuad,
|
||||
FmtX51, /* (WFX order) */
|
||||
FmtX61, /* (WFX order) */
|
||||
FmtX71, /* (WFX order) */
|
||||
FmtBFormat2D,
|
||||
FmtBFormat3D,
|
||||
FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */
|
||||
FmtUHJ3, /* 3-channel UHJ, aka "THJ" */
|
||||
FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */
|
||||
FmtSuperStereo, /* Stereo processed with Super Stereo. */
|
||||
};
|
||||
|
||||
enum class AmbiLayout : unsigned char {
|
||||
FuMa,
|
||||
ACN,
|
||||
};
|
||||
enum class AmbiScaling : unsigned char {
|
||||
FuMa,
|
||||
SN3D,
|
||||
N3D,
|
||||
UHJ,
|
||||
};
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept;
|
||||
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept;
|
||||
inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept
|
||||
{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); }
|
||||
|
||||
constexpr bool IsBFormat(FmtChannels chans) noexcept
|
||||
{ return chans == FmtBFormat2D || chans == FmtBFormat3D; }
|
||||
|
||||
/* Super Stereo is considered part of the UHJ family here, since it goes
|
||||
* through similar processing as UHJ, both result in a B-Format signal, and
|
||||
* needs the same consideration as BHJ (three channel result with only two
|
||||
* channel input).
|
||||
*/
|
||||
constexpr bool IsUHJ(FmtChannels chans) noexcept
|
||||
{ return chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtUHJ4 || chans == FmtSuperStereo; }
|
||||
|
||||
/** Ambisonic formats are either B-Format or UHJ formats. */
|
||||
constexpr bool IsAmbisonic(FmtChannels chans) noexcept
|
||||
{ return IsBFormat(chans) || IsUHJ(chans); }
|
||||
|
||||
constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept
|
||||
{
|
||||
return chans == FmtBFormat2D || chans == FmtUHJ2 || chans == FmtUHJ3
|
||||
|| chans == FmtSuperStereo;
|
||||
}
|
||||
|
||||
|
||||
using CallbackType = int(*)(void*, void*, int);
|
||||
|
||||
struct BufferStorage {
|
||||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
uint mSampleRate{0u};
|
||||
FmtChannels mChannels{FmtMono};
|
||||
FmtType mType{FmtShort};
|
||||
uint mSampleLen{0u};
|
||||
|
||||
AmbiLayout mAmbiLayout{AmbiLayout::FuMa};
|
||||
AmbiScaling mAmbiScaling{AmbiScaling::FuMa};
|
||||
uint mAmbiOrder{0u};
|
||||
|
||||
inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); }
|
||||
inline uint channelsFromFmt() const noexcept
|
||||
{ return ChannelsFromFmt(mChannels, mAmbiOrder); }
|
||||
inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
|
||||
|
||||
inline bool isBFormat() const noexcept { return IsBFormat(mChannels); }
|
||||
};
|
||||
|
||||
#endif /* CORE_BUFFER_STORAGE_H */
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <array>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
/* Size for temporary storage of buffer data, in floats. Larger values need
|
||||
* more memory and are harder on cache, while smaller values may need more
|
||||
* iterations for mixing.
|
||||
|
|
@ -10,5 +12,6 @@
|
|||
constexpr int BufferLineSize{1024};
|
||||
|
||||
using FloatBufferLine = std::array<float,BufferLineSize>;
|
||||
using FloatBufferSpan = al::span<float,BufferLineSize>;
|
||||
|
||||
#endif /* CORE_BUFFERLINE_H */
|
||||
|
|
|
|||
138
Engine/lib/openal-soft/core/context.cpp
Normal file
138
Engine/lib/openal-soft/core/context.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "async_event.h"
|
||||
#include "context.h"
|
||||
#include "device.h"
|
||||
#include "effectslot.h"
|
||||
#include "logging.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "voice.h"
|
||||
#include "voice_change.h"
|
||||
|
||||
|
||||
ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
|
||||
{ }
|
||||
|
||||
ContextBase::~ContextBase()
|
||||
{
|
||||
size_t count{0};
|
||||
ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)};
|
||||
if(cprops)
|
||||
{
|
||||
++count;
|
||||
delete cprops;
|
||||
}
|
||||
cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire);
|
||||
while(cprops)
|
||||
{
|
||||
std::unique_ptr<ContextProps> old{cprops};
|
||||
cprops = old->next.load(std::memory_order_relaxed);
|
||||
++count;
|
||||
}
|
||||
TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s");
|
||||
|
||||
count = 0;
|
||||
EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)};
|
||||
while(eprops)
|
||||
{
|
||||
std::unique_ptr<EffectSlotProps> old{eprops};
|
||||
eprops = old->next.load(std::memory_order_relaxed);
|
||||
++count;
|
||||
}
|
||||
TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
|
||||
|
||||
if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)})
|
||||
{
|
||||
al::destroy_n(curarray->end(), curarray->size());
|
||||
delete curarray;
|
||||
}
|
||||
|
||||
delete mVoices.exchange(nullptr, std::memory_order_relaxed);
|
||||
|
||||
if(mAsyncEvents)
|
||||
{
|
||||
count = 0;
|
||||
auto evt_vec = mAsyncEvents->getReadVector();
|
||||
if(evt_vec.first.len > 0)
|
||||
{
|
||||
al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), evt_vec.first.len);
|
||||
count += evt_vec.first.len;
|
||||
}
|
||||
if(evt_vec.second.len > 0)
|
||||
{
|
||||
al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.second.buf), evt_vec.second.len);
|
||||
count += evt_vec.second.len;
|
||||
}
|
||||
if(count > 0)
|
||||
TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s");
|
||||
mAsyncEvents->readAdvance(count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ContextBase::allocVoiceChanges()
|
||||
{
|
||||
constexpr size_t clustersize{128};
|
||||
|
||||
VoiceChangeCluster cluster{std::make_unique<VoiceChange[]>(clustersize)};
|
||||
for(size_t i{1};i < clustersize;++i)
|
||||
cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed);
|
||||
cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed);
|
||||
|
||||
mVoiceChangeClusters.emplace_back(std::move(cluster));
|
||||
mVoiceChangeTail = mVoiceChangeClusters.back().get();
|
||||
}
|
||||
|
||||
void ContextBase::allocVoiceProps()
|
||||
{
|
||||
constexpr size_t clustersize{32};
|
||||
|
||||
TRACE("Increasing allocated voice properties to %zu\n",
|
||||
(mVoicePropClusters.size()+1) * clustersize);
|
||||
|
||||
VoicePropsCluster cluster{std::make_unique<VoicePropsItem[]>(clustersize)};
|
||||
for(size_t i{1};i < clustersize;++i)
|
||||
cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed);
|
||||
mVoicePropClusters.emplace_back(std::move(cluster));
|
||||
|
||||
VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)};
|
||||
do {
|
||||
mVoicePropClusters.back()[clustersize-1].next.store(oldhead, std::memory_order_relaxed);
|
||||
} while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back().get(),
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == false);
|
||||
}
|
||||
|
||||
void ContextBase::allocVoices(size_t addcount)
|
||||
{
|
||||
constexpr size_t clustersize{32};
|
||||
/* Convert element count to cluster count. */
|
||||
addcount = (addcount+(clustersize-1)) / clustersize;
|
||||
|
||||
if(addcount >= std::numeric_limits<int>::max()/clustersize - mVoiceClusters.size())
|
||||
throw std::runtime_error{"Allocating too many voices"};
|
||||
const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize};
|
||||
TRACE("Increasing allocated voices to %zu\n", totalcount);
|
||||
|
||||
auto newarray = VoiceArray::Create(totalcount);
|
||||
while(addcount)
|
||||
{
|
||||
mVoiceClusters.emplace_back(std::make_unique<Voice[]>(clustersize));
|
||||
--addcount;
|
||||
}
|
||||
|
||||
auto voice_iter = newarray->begin();
|
||||
for(VoiceCluster &cluster : mVoiceClusters)
|
||||
{
|
||||
for(size_t i{0};i < clustersize;++i)
|
||||
*(voice_iter++) = &cluster[i];
|
||||
}
|
||||
|
||||
if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel))
|
||||
{
|
||||
mDevice->waitForMix();
|
||||
delete oldvoices;
|
||||
}
|
||||
}
|
||||
172
Engine/lib/openal-soft/core/context.h
Normal file
172
Engine/lib/openal-soft/core/context.h
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#ifndef CORE_CONTEXT_H
|
||||
#define CORE_CONTEXT_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "bufferline.h"
|
||||
#include "threads.h"
|
||||
#include "vecmat.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct DeviceBase;
|
||||
struct EffectSlot;
|
||||
struct EffectSlotProps;
|
||||
struct RingBuffer;
|
||||
struct Voice;
|
||||
struct VoiceChange;
|
||||
struct VoicePropsItem;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
constexpr float SpeedOfSoundMetersPerSec{343.3f};
|
||||
|
||||
constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */
|
||||
|
||||
enum class DistanceModel : unsigned char {
|
||||
Disable,
|
||||
Inverse, InverseClamped,
|
||||
Linear, LinearClamped,
|
||||
Exponent, ExponentClamped,
|
||||
|
||||
Default = InverseClamped
|
||||
};
|
||||
|
||||
|
||||
struct WetBuffer {
|
||||
bool mInUse;
|
||||
al::FlexArray<FloatBufferLine, 16> mBuffer;
|
||||
|
||||
WetBuffer(size_t count) : mBuffer{count} { }
|
||||
|
||||
DEF_FAM_NEWDEL(WetBuffer, mBuffer)
|
||||
};
|
||||
using WetBufferPtr = std::unique_ptr<WetBuffer>;
|
||||
|
||||
|
||||
struct ContextProps {
|
||||
std::array<float,3> Position;
|
||||
std::array<float,3> Velocity;
|
||||
std::array<float,3> OrientAt;
|
||||
std::array<float,3> OrientUp;
|
||||
float Gain;
|
||||
float MetersPerUnit;
|
||||
float AirAbsorptionGainHF;
|
||||
|
||||
float DopplerFactor;
|
||||
float DopplerVelocity;
|
||||
float SpeedOfSound;
|
||||
bool SourceDistanceModel;
|
||||
DistanceModel mDistanceModel;
|
||||
|
||||
std::atomic<ContextProps*> next;
|
||||
|
||||
DEF_NEWDEL(ContextProps)
|
||||
};
|
||||
|
||||
struct ContextParams {
|
||||
/* Pointer to the most recent property values that are awaiting an update. */
|
||||
std::atomic<ContextProps*> ContextUpdate{nullptr};
|
||||
|
||||
alu::Vector Position{};
|
||||
alu::Matrix Matrix{alu::Matrix::Identity()};
|
||||
alu::Vector Velocity{};
|
||||
|
||||
float Gain{1.0f};
|
||||
float MetersPerUnit{1.0f};
|
||||
float AirAbsorptionGainHF{AirAbsorbGainHF};
|
||||
|
||||
float DopplerFactor{1.0f};
|
||||
float SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */
|
||||
|
||||
bool SourceDistanceModel{false};
|
||||
DistanceModel mDistanceModel{};
|
||||
};
|
||||
|
||||
struct ContextBase {
|
||||
DeviceBase *const mDevice;
|
||||
|
||||
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
|
||||
* indicates if updates are currently happening).
|
||||
*/
|
||||
RefCount mUpdateCount{0u};
|
||||
std::atomic<bool> mHoldUpdates{false};
|
||||
std::atomic<bool> mStopVoicesOnDisconnect{true};
|
||||
|
||||
float mGainBoost{1.0f};
|
||||
|
||||
/* Linked lists of unused property containers, free to use for future
|
||||
* updates.
|
||||
*/
|
||||
std::atomic<ContextProps*> mFreeContextProps{nullptr};
|
||||
std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
|
||||
std::atomic<EffectSlotProps*> mFreeEffectslotProps{nullptr};
|
||||
|
||||
/* The voice change tail is the beginning of the "free" elements, up to and
|
||||
* *excluding* the current. If tail==current, there's no free elements and
|
||||
* new ones need to be allocated. The current voice change is the element
|
||||
* last processed, and any after are pending.
|
||||
*/
|
||||
VoiceChange *mVoiceChangeTail{};
|
||||
std::atomic<VoiceChange*> mCurrentVoiceChange{};
|
||||
|
||||
void allocVoiceChanges();
|
||||
void allocVoiceProps();
|
||||
|
||||
|
||||
ContextParams mParams;
|
||||
|
||||
using VoiceArray = al::FlexArray<Voice*>;
|
||||
std::atomic<VoiceArray*> mVoices{};
|
||||
std::atomic<size_t> mActiveVoiceCount{};
|
||||
|
||||
void allocVoices(size_t addcount);
|
||||
al::span<Voice*> getVoicesSpan() const noexcept
|
||||
{
|
||||
return {mVoices.load(std::memory_order_relaxed)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_relaxed)};
|
||||
}
|
||||
al::span<Voice*> getVoicesSpanAcquired() const noexcept
|
||||
{
|
||||
return {mVoices.load(std::memory_order_acquire)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_acquire)};
|
||||
}
|
||||
|
||||
|
||||
using EffectSlotArray = al::FlexArray<EffectSlot*>;
|
||||
std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};
|
||||
|
||||
std::thread mEventThread;
|
||||
al::semaphore mEventSem;
|
||||
std::unique_ptr<RingBuffer> mAsyncEvents;
|
||||
std::atomic<uint> mEnabledEvts{0u};
|
||||
|
||||
/* Asynchronous voice change actions are processed as a linked list of
|
||||
* VoiceChange objects by the mixer, which is atomically appended to.
|
||||
* However, to avoid allocating each object individually, they're allocated
|
||||
* in clusters that are stored in a vector for easy automatic cleanup.
|
||||
*/
|
||||
using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
|
||||
al::vector<VoiceChangeCluster> mVoiceChangeClusters;
|
||||
|
||||
using VoiceCluster = std::unique_ptr<Voice[]>;
|
||||
al::vector<VoiceCluster> mVoiceClusters;
|
||||
|
||||
using VoicePropsCluster = std::unique_ptr<VoicePropsItem[]>;
|
||||
al::vector<VoicePropsCluster> mVoicePropClusters;
|
||||
|
||||
|
||||
ContextBase(DeviceBase *device);
|
||||
ContextBase(const ContextBase&) = delete;
|
||||
ContextBase& operator=(const ContextBase&) = delete;
|
||||
~ContextBase();
|
||||
};
|
||||
|
||||
#endif /* CORE_CONTEXT_H */
|
||||
371
Engine/lib/openal-soft/core/converter.cpp
Normal file
371
Engine/lib/openal-soft/core/converter.cpp
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits.h>
|
||||
|
||||
#include "albit.h"
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "fpu_ctrl.h"
|
||||
|
||||
struct CTag;
|
||||
struct CopyTag;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint MaxPitch{10};
|
||||
|
||||
static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
|
||||
static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
|
||||
"MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
|
||||
|
||||
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
|
||||
* chokes on that given the inline specializations.
|
||||
*/
|
||||
template<DevFmtType T>
|
||||
inline float LoadSample(DevFmtType_t<T> val) noexcept;
|
||||
|
||||
template<> inline float LoadSample<DevFmtByte>(DevFmtType_t<DevFmtByte> val) noexcept
|
||||
{ return val * (1.0f/128.0f); }
|
||||
template<> inline float LoadSample<DevFmtShort>(DevFmtType_t<DevFmtShort> val) noexcept
|
||||
{ return val * (1.0f/32768.0f); }
|
||||
template<> inline float LoadSample<DevFmtInt>(DevFmtType_t<DevFmtInt> val) noexcept
|
||||
{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
|
||||
template<> inline float LoadSample<DevFmtFloat>(DevFmtType_t<DevFmtFloat> val) noexcept
|
||||
{ return val; }
|
||||
|
||||
template<> inline float LoadSample<DevFmtUByte>(DevFmtType_t<DevFmtUByte> val) noexcept
|
||||
{ return LoadSample<DevFmtByte>(static_cast<int8_t>(val - 128)); }
|
||||
template<> inline float LoadSample<DevFmtUShort>(DevFmtType_t<DevFmtUShort> val) noexcept
|
||||
{ return LoadSample<DevFmtShort>(static_cast<int16_t>(val - 32768)); }
|
||||
template<> inline float LoadSample<DevFmtUInt>(DevFmtType_t<DevFmtUInt> val) noexcept
|
||||
{ return LoadSample<DevFmtInt>(static_cast<int32_t>(val - 2147483648u)); }
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
dst[i] = LoadSample<T>(ssrc[i*srcstep]);
|
||||
}
|
||||
|
||||
void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline DevFmtType_t<T> StoreSample(float) noexcept;
|
||||
|
||||
template<> inline float StoreSample<DevFmtFloat>(float val) noexcept
|
||||
{ return val; }
|
||||
template<> inline int32_t StoreSample<DevFmtInt>(float val) noexcept
|
||||
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
|
||||
template<> inline int16_t StoreSample<DevFmtShort>(float val) noexcept
|
||||
{ return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
|
||||
template<> inline int8_t StoreSample<DevFmtByte>(float val) noexcept
|
||||
{ return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
|
||||
|
||||
/* Define unsigned output variations. */
|
||||
template<> inline uint32_t StoreSample<DevFmtUInt>(float val) noexcept
|
||||
{ return static_cast<uint32_t>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
|
||||
template<> inline uint16_t StoreSample<DevFmtUShort>(float val) noexcept
|
||||
{ return static_cast<uint16_t>(StoreSample<DevFmtShort>(val) + 32768); }
|
||||
template<> inline uint8_t StoreSample<DevFmtUByte>(float val) noexcept
|
||||
{ return static_cast<uint8_t>(StoreSample<DevFmtByte>(val) + 128); }
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
DevFmtType_t<T> *sdst = static_cast<DevFmtType_t<T>*>(dst);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
sdst[i*dststep] = StoreSample<T>(src[i]);
|
||||
}
|
||||
|
||||
|
||||
void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
|
||||
switch(dsttype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
|
||||
}
|
||||
|
||||
template<DevFmtType T>
|
||||
void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst,
|
||||
const void *src, const size_t frames) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
std::fill_n(dst, frames, 0.0f);
|
||||
for(size_t c{0};chanmask;++c)
|
||||
{
|
||||
if LIKELY((chanmask&1))
|
||||
{
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] += LoadSample<T>(ssrc[i*step + c]);
|
||||
}
|
||||
chanmask >>= 1;
|
||||
}
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] *= scale;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler)
|
||||
{
|
||||
if(numchans < 1 || srcRate < 1 || dstRate < 1)
|
||||
return nullptr;
|
||||
|
||||
SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}};
|
||||
converter->mSrcType = srcType;
|
||||
converter->mDstType = dstType;
|
||||
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
|
||||
converter->mDstTypeSize = BytesFromDevFmt(dstType);
|
||||
|
||||
converter->mSrcPrepCount = 0;
|
||||
converter->mFracOffset = 0;
|
||||
|
||||
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
|
||||
FPUCtl mixer_mode{};
|
||||
auto step = static_cast<uint>(
|
||||
mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne));
|
||||
converter->mIncrement = maxu(step, 1);
|
||||
if(converter->mIncrement == MixerFracOne)
|
||||
converter->mResample = Resample_<CopyTag,CTag>;
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
uint SampleConverter::availableOut(uint srcframes) const
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= srcframes)
|
||||
return 0;
|
||||
srcframes -= static_cast<uint>(-prepcount);
|
||||
prepcount = 0;
|
||||
}
|
||||
|
||||
if(srcframes < 1)
|
||||
{
|
||||
/* No output samples if there's no input samples. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= srcframes)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += srcframes;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
DataSize64 -= mFracOffset;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
return static_cast<uint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1,
|
||||
std::numeric_limits<int>::max()));
|
||||
}
|
||||
|
||||
uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes)
|
||||
{
|
||||
const uint SrcFrameSize{static_cast<uint>(mChan.size()) * mSrcTypeSize};
|
||||
const uint DstFrameSize{static_cast<uint>(mChan.size()) * mDstTypeSize};
|
||||
const uint increment{mIncrement};
|
||||
auto SamplesIn = static_cast<const al::byte*>(*src);
|
||||
uint NumSrcSamples{*srcframes};
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
uint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= NumSrcSamples)
|
||||
{
|
||||
mSrcPrepCount = static_cast<int>(NumSrcSamples) + prepcount;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
SamplesIn += SrcFrameSize*static_cast<uint>(-prepcount);
|
||||
NumSrcSamples -= static_cast<uint>(-prepcount);
|
||||
mSrcPrepCount = 0;
|
||||
continue;
|
||||
}
|
||||
const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)};
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= toread)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. Store
|
||||
* what we're given for later.
|
||||
*/
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
|
||||
mChan.size(), mSrcType, toread);
|
||||
|
||||
mSrcPrepCount = prepcount + static_cast<int>(toread);
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
float *RESTRICT SrcData{mSrcSamples};
|
||||
float *RESTRICT DstData{mDstSamples};
|
||||
uint DataPosFrac{mFracOffset};
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += toread;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
DataSize64 -= DataPosFrac;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
auto DstSize = static_cast<uint>(
|
||||
clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize));
|
||||
DstSize = minu(DstSize, dstframes-pos);
|
||||
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
{
|
||||
const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
|
||||
al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan;
|
||||
|
||||
/* Load the previous samples into the source data first, then the
|
||||
* new samples from the input buffer.
|
||||
*/
|
||||
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
|
||||
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits};
|
||||
if(SrcDataEnd >= static_cast<uint>(prepcount)+toread)
|
||||
std::fill(std::begin(mChan[chan].PrevSamples),
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
else
|
||||
{
|
||||
const size_t len{minz(al::size(mChan[chan].PrevSamples),
|
||||
static_cast<uint>(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 float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1),
|
||||
DataPosFrac, increment, {DstData, DstSize})};
|
||||
|
||||
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
DataPosFrac += increment*DstSize;
|
||||
mSrcPrepCount = mini(prepcount + static_cast<int>(toread - (DataPosFrac>>MixerFracBits)),
|
||||
MaxResamplerPadding);
|
||||
mFracOffset = DataPosFrac & MixerFracMask;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits);
|
||||
NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits));
|
||||
|
||||
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
}
|
||||
|
||||
*src = SamplesIn;
|
||||
*srcframes = NumSrcSamples;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
void ChannelConverter::convert(const void *src, float *dst, uint frames) const
|
||||
{
|
||||
if(mDstChans == DevFmtMono)
|
||||
{
|
||||
const float scale{std::sqrt(1.0f / static_cast<float>(al::popcount(mChanMask)))};
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Multi2Mono<T>(mChanMask, mSrcStep, scale, 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(mChanMask == 0x1 && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Engine/lib/openal-soft/core/converter.h
Normal file
59
Engine/lib/openal-soft/core/converter.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef CORE_CONVERTER_H
|
||||
#define CORE_CONVERTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "devformat.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
struct SampleConverter {
|
||||
DevFmtType mSrcType{};
|
||||
DevFmtType mDstType{};
|
||||
uint mSrcTypeSize{};
|
||||
uint mDstTypeSize{};
|
||||
|
||||
int mSrcPrepCount{};
|
||||
|
||||
uint mFracOffset{};
|
||||
uint mIncrement{};
|
||||
InterpState mState{};
|
||||
ResamplerFunc mResample{};
|
||||
|
||||
alignas(16) float mSrcSamples[BufferLineSize]{};
|
||||
alignas(16) float mDstSamples[BufferLineSize]{};
|
||||
|
||||
struct ChanSamples {
|
||||
alignas(16) float PrevSamples[MaxResamplerPadding];
|
||||
};
|
||||
al::FlexArray<ChanSamples> mChan;
|
||||
|
||||
SampleConverter(size_t numchans) : mChan{numchans} { }
|
||||
|
||||
uint convert(const void **src, uint *srcframes, void *dst, uint dstframes);
|
||||
uint availableOut(uint srcframes) const;
|
||||
|
||||
DEF_FAM_NEWDEL(SampleConverter, mChan)
|
||||
};
|
||||
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler);
|
||||
|
||||
|
||||
struct ChannelConverter {
|
||||
DevFmtType mSrcType{};
|
||||
uint mSrcStep{};
|
||||
uint mChanMask{};
|
||||
DevFmtChannels mDstChans{};
|
||||
|
||||
bool is_active() const noexcept { return mChanMask != 0; }
|
||||
|
||||
void convert(const void *src, float *dst, uint frames) const;
|
||||
};
|
||||
|
||||
#endif /* CORE_CONVERTER_H */
|
||||
46
Engine/lib/openal-soft/core/dbus_wrap.cpp
Normal file
46
Engine/lib/openal-soft/core/dbus_wrap.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
void *dbus_handle{nullptr};
|
||||
#define DECL_FUNC(x) decltype(p##x) p##x{};
|
||||
DBUS_FUNCTIONS(DECL_FUNC)
|
||||
#undef DECL_FUNC
|
||||
|
||||
void PrepareDBus()
|
||||
{
|
||||
static constexpr char libname[] = "libdbus-1.so.3";
|
||||
|
||||
auto load_func = [](auto &f, const char *name) -> void
|
||||
{ f = reinterpret_cast<std::remove_reference_t<decltype(f)>>(GetSymbol(dbus_handle, name)); };
|
||||
#define LOAD_FUNC(x) do { \
|
||||
load_func(p##x, #x); \
|
||||
if(!p##x) \
|
||||
{ \
|
||||
WARN("Failed to load function %s\n", #x); \
|
||||
CloseLib(dbus_handle); \
|
||||
dbus_handle = nullptr; \
|
||||
return; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
dbus_handle = LoadLib(libname);
|
||||
if(!dbus_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", libname);
|
||||
return;
|
||||
}
|
||||
|
||||
DBUS_FUNCTIONS(LOAD_FUNC)
|
||||
#undef LOAD_FUNC
|
||||
}
|
||||
#endif
|
||||
87
Engine/lib/openal-soft/core/dbus_wrap.h
Normal file
87
Engine/lib/openal-soft/core/dbus_wrap.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#ifndef CORE_DBUS_WRAP_H
|
||||
#define CORE_DBUS_WRAP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#include "dynload.h"
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define DBUS_FUNCTIONS(MAGIC) \
|
||||
MAGIC(dbus_error_init) \
|
||||
MAGIC(dbus_error_free) \
|
||||
MAGIC(dbus_bus_get) \
|
||||
MAGIC(dbus_connection_set_exit_on_disconnect) \
|
||||
MAGIC(dbus_connection_unref) \
|
||||
MAGIC(dbus_connection_send_with_reply_and_block) \
|
||||
MAGIC(dbus_message_unref) \
|
||||
MAGIC(dbus_message_new_method_call) \
|
||||
MAGIC(dbus_message_append_args) \
|
||||
MAGIC(dbus_message_iter_init) \
|
||||
MAGIC(dbus_message_iter_next) \
|
||||
MAGIC(dbus_message_iter_recurse) \
|
||||
MAGIC(dbus_message_iter_get_arg_type) \
|
||||
MAGIC(dbus_message_iter_get_basic) \
|
||||
MAGIC(dbus_set_error_from_message)
|
||||
|
||||
extern void *dbus_handle;
|
||||
#define DECL_FUNC(x) extern decltype(x) *p##x;
|
||||
DBUS_FUNCTIONS(DECL_FUNC)
|
||||
#undef DECL_FUNC
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define dbus_error_init (*pdbus_error_init)
|
||||
#define dbus_error_free (*pdbus_error_free)
|
||||
#define dbus_bus_get (*pdbus_bus_get)
|
||||
#define dbus_connection_set_exit_on_disconnect (*pdbus_connection_set_exit_on_disconnect)
|
||||
#define dbus_connection_unref (*pdbus_connection_unref)
|
||||
#define dbus_connection_send_with_reply_and_block (*pdbus_connection_send_with_reply_and_block)
|
||||
#define dbus_message_unref (*pdbus_message_unref)
|
||||
#define dbus_message_new_method_call (*pdbus_message_new_method_call)
|
||||
#define dbus_message_append_args (*pdbus_message_append_args)
|
||||
#define dbus_message_iter_init (*pdbus_message_iter_init)
|
||||
#define dbus_message_iter_next (*pdbus_message_iter_next)
|
||||
#define dbus_message_iter_recurse (*pdbus_message_iter_recurse)
|
||||
#define dbus_message_iter_get_arg_type (*pdbus_message_iter_get_arg_type)
|
||||
#define dbus_message_iter_get_basic (*pdbus_message_iter_get_basic)
|
||||
#define dbus_set_error_from_message (*pdbus_set_error_from_message)
|
||||
#endif
|
||||
|
||||
void PrepareDBus();
|
||||
|
||||
inline auto HasDBus()
|
||||
{
|
||||
static std::once_flag init_dbus{};
|
||||
std::call_once(init_dbus, []{ PrepareDBus(); });
|
||||
return dbus_handle;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
constexpr bool HasDBus() noexcept { return true; }
|
||||
#endif /* HAVE_DYNLOAD */
|
||||
|
||||
|
||||
namespace dbus {
|
||||
|
||||
struct Error {
|
||||
Error() { dbus_error_init(&mError); }
|
||||
~Error() { dbus_error_free(&mError); }
|
||||
DBusError* operator->() { return &mError; }
|
||||
DBusError &get() { return mError; }
|
||||
private:
|
||||
DBusError mError{};
|
||||
};
|
||||
|
||||
struct ConnectionDeleter {
|
||||
void operator()(DBusConnection *c) { dbus_connection_unref(c); }
|
||||
};
|
||||
using ConnectionPtr = std::unique_ptr<DBusConnection,ConnectionDeleter>;
|
||||
|
||||
} // namespace dbus
|
||||
|
||||
#endif /* CORE_DBUS_WRAP_H */
|
||||
|
|
@ -26,7 +26,6 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept
|
|||
case DevFmtStereo: return 2;
|
||||
case DevFmtQuad: return 4;
|
||||
case DevFmtX51: return 6;
|
||||
case DevFmtX51Rear: return 6;
|
||||
case DevFmtX61: return 7;
|
||||
case DevFmtX71: return 8;
|
||||
case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1);
|
||||
|
|
@ -56,7 +55,6 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept
|
|||
case DevFmtStereo: return "Stereo";
|
||||
case DevFmtQuad: return "Quadraphonic";
|
||||
case DevFmtX51: return "5.1 Surround";
|
||||
case DevFmtX51Rear: return "5.1 Surround (Rear)";
|
||||
case DevFmtX61: return "6.1 Surround";
|
||||
case DevFmtX71: return "7.1 Surround";
|
||||
case DevFmtAmbi3D: return "Ambisonic 3D";
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ enum Channel : unsigned char {
|
|||
SideLeft,
|
||||
SideRight,
|
||||
|
||||
TopCenter,
|
||||
TopFrontLeft,
|
||||
TopFrontCenter,
|
||||
TopFrontRight,
|
||||
TopCenter,
|
||||
TopBackLeft,
|
||||
TopBackCenter,
|
||||
TopBackRight,
|
||||
|
|
@ -50,9 +50,6 @@ enum DevFmtChannels : unsigned char {
|
|||
DevFmtX71,
|
||||
DevFmtAmbi3D,
|
||||
|
||||
/* Similar to 5.1, except using rear channels instead of sides */
|
||||
DevFmtX51Rear,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
};
|
||||
#define MAX_OUTPUT_CHANNELS 16
|
||||
|
|
|
|||
23
Engine/lib/openal-soft/core/device.cpp
Normal file
23
Engine/lib/openal-soft/core/device.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
#include "bs2b.h"
|
||||
#include "device.h"
|
||||
#include "front_stablizer.h"
|
||||
#include "hrtf.h"
|
||||
#include "mastering.h"
|
||||
|
||||
|
||||
al::FlexArray<ContextBase*> DeviceBase::sEmptyContextArray{0u};
|
||||
|
||||
|
||||
DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray}
|
||||
{
|
||||
}
|
||||
|
||||
DeviceBase::~DeviceBase()
|
||||
{
|
||||
auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed);
|
||||
if(oldarray != &sEmptyContextArray) delete oldarray;
|
||||
}
|
||||
310
Engine/lib/openal-soft/core/device.h
Normal file
310
Engine/lib/openal-soft/core/device.h
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
#ifndef CORE_DEVICE_H
|
||||
#define CORE_DEVICE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "atomic.h"
|
||||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/nfc.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "opthelpers.h"
|
||||
#include "resampler_limits.h"
|
||||
#include "uhjfilter.h"
|
||||
#include "vector.h"
|
||||
|
||||
class BFormatDec;
|
||||
struct bs2b;
|
||||
struct Compressor;
|
||||
struct ContextBase;
|
||||
struct DirectHrtfState;
|
||||
struct HrtfStore;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
#define MIN_OUTPUT_RATE 8000
|
||||
#define MAX_OUTPUT_RATE 192000
|
||||
#define DEFAULT_OUTPUT_RATE 44100
|
||||
|
||||
#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
|
||||
#define DEFAULT_NUM_UPDATES 3
|
||||
|
||||
|
||||
enum class DeviceType : unsigned char {
|
||||
Playback,
|
||||
Capture,
|
||||
Loopback
|
||||
};
|
||||
|
||||
|
||||
enum class RenderMode : unsigned char {
|
||||
Normal,
|
||||
Pairwise,
|
||||
Hrtf
|
||||
};
|
||||
|
||||
enum class StereoEncoding : unsigned char {
|
||||
Basic,
|
||||
Uhj,
|
||||
Hrtf,
|
||||
|
||||
Default = Basic
|
||||
};
|
||||
|
||||
|
||||
struct InputRemixMap {
|
||||
struct TargetMix { Channel channel; float mix; };
|
||||
|
||||
Channel channel;
|
||||
std::array<TargetMix,2> targets;
|
||||
};
|
||||
|
||||
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
#define MAX_DELAY_LENGTH 1024
|
||||
|
||||
struct DistanceComp {
|
||||
struct ChanData {
|
||||
float Gain{1.0f};
|
||||
uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
|
||||
float *Buffer{nullptr};
|
||||
};
|
||||
|
||||
std::array<ChanData,MAX_OUTPUT_CHANNELS> mChannels;
|
||||
al::FlexArray<float,16> mSamples;
|
||||
|
||||
DistanceComp(size_t count) : mSamples{count} { }
|
||||
|
||||
static std::unique_ptr<DistanceComp> Create(size_t numsamples)
|
||||
{ return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(DistanceComp, mSamples)
|
||||
};
|
||||
|
||||
|
||||
struct BFChannelConfig {
|
||||
float Scale;
|
||||
uint Index;
|
||||
};
|
||||
|
||||
|
||||
struct MixParams {
|
||||
/* Coefficient channel mapping for mixing to the buffer. */
|
||||
std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
struct RealMixParams {
|
||||
al::span<const InputRemixMap> RemixMap;
|
||||
std::array<uint,MaxChannels> ChannelIndex{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
enum {
|
||||
// Frequency was requested by the app or config file
|
||||
FrequencyRequest,
|
||||
// Channel configuration was requested by the config file
|
||||
ChannelsRequest,
|
||||
// Sample type was requested by the config file
|
||||
SampleTypeRequest,
|
||||
|
||||
// Specifies if the DSP is paused at user request
|
||||
DevicePaused,
|
||||
// Specifies if the device is currently running
|
||||
DeviceRunning,
|
||||
|
||||
// Specifies if the output plays directly on/in ears (headphones, headset,
|
||||
// ear buds, etc).
|
||||
DirectEar,
|
||||
|
||||
DeviceFlagsCount
|
||||
};
|
||||
|
||||
struct DeviceBase {
|
||||
/* To avoid extraneous allocations, a 0-sized FlexArray<ContextBase*> is
|
||||
* defined globally as a sharable object.
|
||||
*/
|
||||
static al::FlexArray<ContextBase*> sEmptyContextArray;
|
||||
|
||||
std::atomic<bool> Connected{true};
|
||||
const DeviceType Type{};
|
||||
|
||||
uint Frequency{};
|
||||
uint UpdateSize{};
|
||||
uint BufferSize{};
|
||||
|
||||
DevFmtChannels FmtChans{};
|
||||
DevFmtType FmtType{};
|
||||
uint mAmbiOrder{0};
|
||||
float mXOverFreq{400.0f};
|
||||
/* For DevFmtAmbi* output only, specifies the channel order and
|
||||
* normalization.
|
||||
*/
|
||||
DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
|
||||
DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
|
||||
|
||||
std::string DeviceName;
|
||||
|
||||
// Device flags
|
||||
std::bitset<DeviceFlagsCount> Flags{};
|
||||
|
||||
uint NumAuxSends{};
|
||||
|
||||
/* Rendering mode. */
|
||||
RenderMode mRenderMode{RenderMode::Normal};
|
||||
|
||||
/* The average speaker distance as determined by the ambdec configuration,
|
||||
* HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
|
||||
*/
|
||||
float AvgSpeakerDist{0.0f};
|
||||
|
||||
/* The default NFC filter. Not used directly, but is pre-initialized with
|
||||
* the control distance from AvgSpeakerDist.
|
||||
*/
|
||||
NfcFilter mNFCtrlFilter{};
|
||||
|
||||
uint SamplesDone{0u};
|
||||
std::chrono::nanoseconds ClockBase{0};
|
||||
std::chrono::nanoseconds FixedLatency{0};
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding +
|
||||
UhjDecoder::sFilterDelay};
|
||||
static constexpr size_t MixerChannelsMax{16};
|
||||
using MixerBufferLine = std::array<float,MixerLineSize>;
|
||||
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
|
||||
|
||||
alignas(16) float ResampledData[BufferLineSize];
|
||||
alignas(16) float FilteredData[BufferLineSize];
|
||||
union {
|
||||
alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
|
||||
alignas(16) float NfcSampleData[BufferLineSize];
|
||||
};
|
||||
|
||||
/* Persistent storage for HRTF mixing. */
|
||||
alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength];
|
||||
|
||||
/* Mixing buffer used by the Dry mix and Real output. */
|
||||
al::vector<FloatBufferLine, 16> MixBuffer;
|
||||
|
||||
/* The "dry" path corresponds to the main output. */
|
||||
MixParams Dry;
|
||||
uint NumChannelsPerOrder[MaxAmbiOrder+1]{};
|
||||
|
||||
/* "Real" output, which will be written to the device buffer. May alias the
|
||||
* dry buffer.
|
||||
*/
|
||||
RealMixParams RealOut;
|
||||
|
||||
/* HRTF state and info */
|
||||
std::unique_ptr<DirectHrtfState> mHrtfState;
|
||||
al::intrusive_ptr<HrtfStore> mHrtf;
|
||||
uint mIrSize{0};
|
||||
|
||||
/* Ambisonic-to-UHJ encoder */
|
||||
std::unique_ptr<UhjEncoder> mUhjEncoder;
|
||||
|
||||
/* Ambisonic decoder for speakers */
|
||||
std::unique_ptr<BFormatDec> AmbiDecoder;
|
||||
|
||||
/* Stereo-to-binaural filter */
|
||||
std::unique_ptr<bs2b> Bs2b;
|
||||
|
||||
using PostProc = void(DeviceBase::*)(const size_t SamplesToDo);
|
||||
PostProc PostProcess{nullptr};
|
||||
|
||||
std::unique_ptr<Compressor> Limiter;
|
||||
|
||||
/* Delay buffers used to compensate for speaker distances. */
|
||||
std::unique_ptr<DistanceComp> ChannelDelays;
|
||||
|
||||
/* Dithering control. */
|
||||
float DitherDepth{0.0f};
|
||||
uint DitherSeed{0u};
|
||||
|
||||
/* Running count of the mixer invocations, in 31.1 fixed point. This
|
||||
* actually increments *twice* when mixing, first at the start and then at
|
||||
* the end, so the bottom bit indicates if the device is currently mixing
|
||||
* and the upper bits indicates how many mixes have been done.
|
||||
*/
|
||||
RefCount MixCount{0u};
|
||||
|
||||
// Contexts created on this device
|
||||
std::atomic<al::FlexArray<ContextBase*>*> mContexts{nullptr};
|
||||
|
||||
|
||||
DeviceBase(DeviceType type);
|
||||
DeviceBase(const DeviceBase&) = delete;
|
||||
DeviceBase& operator=(const DeviceBase&) = delete;
|
||||
~DeviceBase();
|
||||
|
||||
uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
|
||||
uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
|
||||
uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
|
||||
|
||||
uint waitForMix() const noexcept
|
||||
{
|
||||
uint refcount;
|
||||
while((refcount=MixCount.load(std::memory_order_acquire))&1) {
|
||||
}
|
||||
return refcount;
|
||||
}
|
||||
|
||||
void ProcessHrtf(const size_t SamplesToDo);
|
||||
void ProcessAmbiDec(const size_t SamplesToDo);
|
||||
void ProcessAmbiDecStablized(const size_t SamplesToDo);
|
||||
void ProcessUhj(const size_t SamplesToDo);
|
||||
void ProcessBs2b(const size_t SamplesToDo);
|
||||
|
||||
inline void postProcess(const size_t SamplesToDo)
|
||||
{ if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); }
|
||||
|
||||
void renderSamples(const al::span<float*> outBuffers, const uint numSamples);
|
||||
void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep);
|
||||
|
||||
/* Caller must lock the device state, and the mixer must not be running. */
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,2,3)]]
|
||||
#else
|
||||
[[gnu::format(printf,2,3)]]
|
||||
#endif
|
||||
void handleDisconnect(const char *msg, ...);
|
||||
|
||||
DISABLE_ALLOC()
|
||||
|
||||
private:
|
||||
uint renderSamples(const uint numSamples);
|
||||
};
|
||||
|
||||
|
||||
/* Must be less than 15 characters (16 including terminating null) for
|
||||
* compatibility with pthread_setname_np limitations. */
|
||||
#define MIXER_THREAD_NAME "alsoft-mixer"
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
|
||||
{ return real.ChannelIndex[chan]; }
|
||||
#define INVALID_CHANNEL_INDEX ~0u
|
||||
|
||||
#endif /* CORE_DEVICE_H */
|
||||
205
Engine/lib/openal-soft/core/effects/base.h
Normal file
205
Engine/lib/openal-soft/core/effects/base.h
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#ifndef CORE_EFFECTS_BASE_H
|
||||
#define CORE_EFFECTS_BASE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct BufferStorage;
|
||||
struct ContextBase;
|
||||
struct DeviceBase;
|
||||
struct EffectSlot;
|
||||
struct MixParams;
|
||||
struct RealMixParams;
|
||||
|
||||
|
||||
/** Target gain for the reverb decay feedback reaching the decay time. */
|
||||
constexpr float ReverbDecayGain{0.001f}; /* -60 dB */
|
||||
|
||||
constexpr float ReverbMaxReflectionsDelay{0.3f};
|
||||
constexpr float ReverbMaxLateReverbDelay{0.1f};
|
||||
|
||||
enum class ChorusWaveform {
|
||||
Sinusoid,
|
||||
Triangle
|
||||
};
|
||||
|
||||
constexpr float ChorusMaxDelay{0.016f};
|
||||
constexpr float FlangerMaxDelay{0.004f};
|
||||
|
||||
constexpr float EchoMaxDelay{0.207f};
|
||||
constexpr float EchoMaxLRDelay{0.404f};
|
||||
|
||||
enum class FShifterDirection {
|
||||
Down,
|
||||
Up,
|
||||
Off
|
||||
};
|
||||
|
||||
enum class ModulatorWaveform {
|
||||
Sinusoid,
|
||||
Sawtooth,
|
||||
Square
|
||||
};
|
||||
|
||||
enum class VMorpherPhenome {
|
||||
A, E, I, O, U,
|
||||
AA, AE, AH, AO, EH, ER, IH, IY, UH, UW,
|
||||
B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z
|
||||
};
|
||||
|
||||
enum class VMorpherWaveform {
|
||||
Sinusoid,
|
||||
Triangle,
|
||||
Sawtooth
|
||||
};
|
||||
|
||||
union EffectProps {
|
||||
struct {
|
||||
// Shared Reverb Properties
|
||||
float Density;
|
||||
float Diffusion;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float DecayTime;
|
||||
float DecayHFRatio;
|
||||
float ReflectionsGain;
|
||||
float ReflectionsDelay;
|
||||
float LateReverbGain;
|
||||
float LateReverbDelay;
|
||||
float AirAbsorptionGainHF;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
|
||||
// Additional EAX Reverb Properties
|
||||
float GainLF;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsPan[3];
|
||||
float LateReverbPan[3];
|
||||
float EchoTime;
|
||||
float EchoDepth;
|
||||
float ModulationTime;
|
||||
float ModulationDepth;
|
||||
float HFReference;
|
||||
float LFReference;
|
||||
} Reverb;
|
||||
|
||||
struct {
|
||||
float AttackTime;
|
||||
float ReleaseTime;
|
||||
float Resonance;
|
||||
float PeakGain;
|
||||
} Autowah;
|
||||
|
||||
struct {
|
||||
ChorusWaveform Waveform;
|
||||
int Phase;
|
||||
float Rate;
|
||||
float Depth;
|
||||
float Feedback;
|
||||
float Delay;
|
||||
} Chorus; /* Also Flanger */
|
||||
|
||||
struct {
|
||||
bool OnOff;
|
||||
} Compressor;
|
||||
|
||||
struct {
|
||||
float Edge;
|
||||
float Gain;
|
||||
float LowpassCutoff;
|
||||
float EQCenter;
|
||||
float EQBandwidth;
|
||||
} Distortion;
|
||||
|
||||
struct {
|
||||
float Delay;
|
||||
float LRDelay;
|
||||
|
||||
float Damping;
|
||||
float Feedback;
|
||||
|
||||
float Spread;
|
||||
} Echo;
|
||||
|
||||
struct {
|
||||
float LowCutoff;
|
||||
float LowGain;
|
||||
float Mid1Center;
|
||||
float Mid1Gain;
|
||||
float Mid1Width;
|
||||
float Mid2Center;
|
||||
float Mid2Gain;
|
||||
float Mid2Width;
|
||||
float HighCutoff;
|
||||
float HighGain;
|
||||
} Equalizer;
|
||||
|
||||
struct {
|
||||
float Frequency;
|
||||
FShifterDirection LeftDirection;
|
||||
FShifterDirection RightDirection;
|
||||
} Fshifter;
|
||||
|
||||
struct {
|
||||
float Frequency;
|
||||
float HighPassCutoff;
|
||||
ModulatorWaveform Waveform;
|
||||
} Modulator;
|
||||
|
||||
struct {
|
||||
int CoarseTune;
|
||||
int FineTune;
|
||||
} Pshifter;
|
||||
|
||||
struct {
|
||||
float Rate;
|
||||
VMorpherPhenome PhonemeA;
|
||||
VMorpherPhenome PhonemeB;
|
||||
int PhonemeACoarseTuning;
|
||||
int PhonemeBCoarseTuning;
|
||||
VMorpherWaveform Waveform;
|
||||
} Vmorpher;
|
||||
|
||||
struct {
|
||||
float Gain;
|
||||
} Dedicated;
|
||||
};
|
||||
|
||||
|
||||
struct EffectTarget {
|
||||
MixParams *Main;
|
||||
RealMixParams *RealOut;
|
||||
};
|
||||
|
||||
struct EffectState : public al::intrusive_ref<EffectState> {
|
||||
struct Buffer {
|
||||
const BufferStorage *storage;
|
||||
al::span<const al::byte> samples;
|
||||
};
|
||||
|
||||
al::span<FloatBufferLine> mOutTarget;
|
||||
|
||||
|
||||
virtual ~EffectState() = default;
|
||||
|
||||
virtual void deviceUpdate(const DeviceBase *device, const Buffer &buffer) = 0;
|
||||
virtual void update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target) = 0;
|
||||
virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct EffectStateFactory {
|
||||
virtual ~EffectStateFactory() = default;
|
||||
|
||||
virtual al::intrusive_ptr<EffectState> create() = 0;
|
||||
};
|
||||
|
||||
#endif /* CORE_EFFECTS_BASE_H */
|
||||
25
Engine/lib/openal-soft/core/effectslot.cpp
Normal file
25
Engine/lib/openal-soft/core/effectslot.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "effectslot.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "context.h"
|
||||
|
||||
|
||||
EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept
|
||||
{
|
||||
/* Allocate space for twice as many pointers, so the mixer has scratch
|
||||
* space to store a sorted list during mixing.
|
||||
*/
|
||||
void *ptr{al_calloc(alignof(EffectSlotArray), EffectSlotArray::Sizeof(count*2))};
|
||||
return al::construct_at(static_cast<EffectSlotArray*>(ptr), count);
|
||||
}
|
||||
|
||||
EffectSlot::~EffectSlot()
|
||||
{
|
||||
if(mWetBuffer)
|
||||
mWetBuffer->mInUse = false;
|
||||
}
|
||||
88
Engine/lib/openal-soft/core/effectslot.h
Normal file
88
Engine/lib/openal-soft/core/effectslot.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef CORE_EFFECTSLOT_H
|
||||
#define CORE_EFFECTSLOT_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "device.h"
|
||||
#include "effects/base.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct EffectSlot;
|
||||
struct WetBuffer;
|
||||
|
||||
using EffectSlotArray = al::FlexArray<EffectSlot*>;
|
||||
|
||||
|
||||
enum class EffectSlotType : unsigned char {
|
||||
None,
|
||||
Reverb,
|
||||
Chorus,
|
||||
Distortion,
|
||||
Echo,
|
||||
Flanger,
|
||||
FrequencyShifter,
|
||||
VocalMorpher,
|
||||
PitchShifter,
|
||||
RingModulator,
|
||||
Autowah,
|
||||
Compressor,
|
||||
Equalizer,
|
||||
EAXReverb,
|
||||
DedicatedLFE,
|
||||
DedicatedDialog,
|
||||
Convolution
|
||||
};
|
||||
|
||||
struct EffectSlotProps {
|
||||
float Gain;
|
||||
bool AuxSendAuto;
|
||||
EffectSlot *Target;
|
||||
|
||||
EffectSlotType Type;
|
||||
EffectProps Props;
|
||||
|
||||
al::intrusive_ptr<EffectState> State;
|
||||
|
||||
std::atomic<EffectSlotProps*> next;
|
||||
|
||||
DEF_NEWDEL(EffectSlotProps)
|
||||
};
|
||||
|
||||
|
||||
struct EffectSlot {
|
||||
std::atomic<EffectSlotProps*> Update{nullptr};
|
||||
|
||||
/* 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;
|
||||
|
||||
float Gain{1.0f};
|
||||
bool AuxSendAuto{true};
|
||||
EffectSlot *Target{nullptr};
|
||||
|
||||
EffectSlotType EffectType{EffectSlotType::None};
|
||||
EffectProps mEffectProps{};
|
||||
EffectState *mEffectState{nullptr};
|
||||
|
||||
float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
|
||||
float DecayTime{0.0f};
|
||||
float DecayLFRatio{0.0f};
|
||||
float DecayHFRatio{0.0f};
|
||||
bool DecayHFLimit{false};
|
||||
float AirAbsorptionGainHF{1.0f};
|
||||
|
||||
/* Mixing buffer used by the Wet mix. */
|
||||
WetBuffer *mWetBuffer{nullptr};
|
||||
|
||||
~EffectSlot();
|
||||
|
||||
static EffectSlotArray *CreatePtrArray(size_t count) noexcept;
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
#endif /* CORE_EFFECTSLOT_H */
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
|||
// Limit gain to -100dB
|
||||
assert(gain > 0.00001f);
|
||||
|
||||
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
|
||||
const Real w0{al::numbers::pi_v<Real>*2.0f * f0norm};
|
||||
const Real sin_w0{std::sin(w0)};
|
||||
const Real cos_w0{std::cos(w0)};
|
||||
const Real alpha{sin_w0/2.0f * rcpQ};
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "alspan.h"
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
/* Filters implementation is based on the "Cookbook formulae for audio
|
||||
|
|
@ -40,11 +40,11 @@ enum class BiquadType {
|
|||
template<typename Real>
|
||||
class BiquadFilterR {
|
||||
/* Last two delayed components for direct form II. */
|
||||
Real mZ1{0.0f}, mZ2{0.0f};
|
||||
Real mZ1{0}, mZ2{0};
|
||||
/* Transfer function coefficients "b" (numerator) */
|
||||
Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f};
|
||||
Real mB0{1}, mB1{0}, mB2{0};
|
||||
/* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
|
||||
Real mA1{0.0f}, mA2{0.0f};
|
||||
Real mA1{0}, mA2{0};
|
||||
|
||||
void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ);
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ class BiquadFilterR {
|
|||
* \param slope 0 < slope <= 1
|
||||
*/
|
||||
static Real rcpQFromSlope(Real gain, Real slope)
|
||||
{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); }
|
||||
{ return std::sqrt((gain + Real{1}/gain)*(Real{1}/slope - Real{1}) + Real{2}); }
|
||||
|
||||
/**
|
||||
* Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the
|
||||
|
|
@ -65,12 +65,12 @@ class BiquadFilterR {
|
|||
*/
|
||||
static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
|
||||
{
|
||||
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
|
||||
return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0));
|
||||
const Real w0{al::numbers::pi_v<Real>*Real{2} * f0norm};
|
||||
return 2.0f*std::sinh(std::log(Real{2})/Real{2}*bandwidth*w0/std::sin(w0));
|
||||
}
|
||||
|
||||
public:
|
||||
void clear() noexcept { mZ1 = mZ2 = 0.0f; }
|
||||
void clear() noexcept { mZ1 = mZ2 = Real{0}; }
|
||||
|
||||
/**
|
||||
* Sets the filter state for the specified filter type and its parameters.
|
||||
|
|
|
|||
|
|
@ -62,26 +62,22 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
|
|||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain = 1.0f / g_0;
|
||||
nfc.a1 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.gain *= g_0;
|
||||
nfc.gain = nfc.base_gain * g_0;
|
||||
nfc.b1 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain /= g_0;
|
||||
nfc.gain /= g_0;
|
||||
nfc.a1 = 2.0f * b_00 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
|
|
@ -102,8 +98,15 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
|
|||
float b_10, b_11, g_1;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.base_gain = 1.0f / g_1;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
|
|
@ -111,21 +114,10 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
|
|||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.gain *= g_1;
|
||||
nfc.gain = nfc.base_gain * g_1;
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.base_gain /= g_1;
|
||||
nfc.gain /= g_1;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
|
|
@ -149,8 +141,18 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
|
|||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
b_00 = B[3][2] * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain = 1.0f / (g_1 * g_0);
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
|
|
@ -160,25 +162,11 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
|
|||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.gain *= g_1 * g_0;
|
||||
nfc.gain = nfc.base_gain * (g_1 * g_0);
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
nfc.b3 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
b_00 = B[3][2] * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.base_gain /= g_1 * g_0;
|
||||
nfc.gain /= g_1 * g_0;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = 2.0f * b_00 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +179,7 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
|
|||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1 * g_0;
|
||||
nfc->gain = nfc->base_gain * (g_1 * g_0);
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = 2.0f * b_00 / g_0;
|
||||
|
|
@ -205,8 +193,20 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
|
|||
float b_00, b_01, g_0;
|
||||
float r;
|
||||
|
||||
nfc.base_gain = 1.0f;
|
||||
nfc.gain = 1.0f;
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[4][0] * r;
|
||||
b_11 = B[4][1] * r * r;
|
||||
b_00 = B[4][2] * r;
|
||||
b_01 = B[4][3] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.base_gain = 1.0f / (g_1 * g_0);
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc.a4 = 4.0f * b_01 / g_0;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
|
|
@ -217,28 +217,12 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
|
|||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.gain *= g_1 * g_0;
|
||||
nfc.gain = nfc.base_gain * (g_1 * g_0);
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc.b4 = 4.0f * b_01 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[4][0] * r;
|
||||
b_11 = B[4][1] * r * r;
|
||||
b_00 = B[4][2] * r;
|
||||
b_01 = B[4][3] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.base_gain /= g_1 * g_0;
|
||||
nfc.gain /= g_1 * g_0;
|
||||
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.a2 = 4.0f * b_11 / g_1;
|
||||
nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc.a4 = 4.0f * b_01 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +236,7 @@ void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
|
|||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00 + b_01};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1 * g_0;
|
||||
nfc->gain = nfc->base_gain * (g_1 * g_0);
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "math_defs.h"
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::init(Real f0norm)
|
||||
{
|
||||
const Real w{f0norm * al::MathDefs<Real>::Tau()};
|
||||
const Real w{f0norm * (al::numbers::pi_v<Real>*2)};
|
||||
const Real cw{std::cos(w)};
|
||||
if(cw > std::numeric_limits<float>::epsilon())
|
||||
mCoeff = (std::sin(w) - 1.0f) / cw;
|
||||
|
|
@ -60,6 +60,41 @@ void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout,
|
|||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::processHfScale(const al::span<const Real> input, Real *RESTRICT output,
|
||||
const Real hfscale)
|
||||
{
|
||||
const Real ap_coeff{mCoeff};
|
||||
const Real lp_coeff{mCoeff*0.5f + 0.5f};
|
||||
Real lp_z1{mLpZ1};
|
||||
Real lp_z2{mLpZ2};
|
||||
Real ap_z1{mApZ1};
|
||||
auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
|
||||
{
|
||||
/* Low-pass sample processing. */
|
||||
Real d{(in - lp_z1) * lp_coeff};
|
||||
Real lp_y{lp_z1 + d};
|
||||
lp_z1 = lp_y + d;
|
||||
|
||||
d = (lp_y - lp_z2) * lp_coeff;
|
||||
lp_y = lp_z2 + d;
|
||||
lp_z2 = lp_y + d;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
ap_z1 = in - ap_y*ap_coeff;
|
||||
|
||||
/* High-pass generated by removing the low-passed signal, which is then
|
||||
* scaled and added back to the low-passed signal.
|
||||
*/
|
||||
return (ap_y-lp_y)*hfscale + lp_y;
|
||||
};
|
||||
std::transform(input.begin(), input.end(), output, proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::processHfScale(const al::span<Real> samples, const Real hfscale)
|
||||
{
|
||||
|
|
@ -95,7 +130,37 @@ void BandSplitterR<Real>::processHfScale(const al::span<Real> samples, const Rea
|
|||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::applyAllpass(const al::span<Real> samples) const
|
||||
void BandSplitterR<Real>::processScale(const al::span<Real> samples, const Real hfscale, const Real lfscale)
|
||||
{
|
||||
const Real ap_coeff{mCoeff};
|
||||
const Real lp_coeff{mCoeff*0.5f + 0.5f};
|
||||
Real lp_z1{mLpZ1};
|
||||
Real lp_z2{mLpZ2};
|
||||
Real ap_z1{mApZ1};
|
||||
auto proc_sample = [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
|
||||
{
|
||||
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;
|
||||
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
ap_z1 = in - ap_y*ap_coeff;
|
||||
|
||||
/* Apply separate factors to the high and low frequencies. */
|
||||
return (ap_y-lp_y)*hfscale + lp_y*lfscale;
|
||||
};
|
||||
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::applyAllpassRev(const al::span<Real> samples) const
|
||||
{
|
||||
const Real coeff{mCoeff};
|
||||
Real z1{0.0f};
|
||||
|
|
@ -105,7 +170,7 @@ void BandSplitterR<Real>::applyAllpass(const al::span<Real> samples) const
|
|||
z1 = in - out*coeff;
|
||||
return out;
|
||||
};
|
||||
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
|
||||
std::transform(samples.rbegin(), samples.rend(), samples.rbegin(), proc_sample);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,18 +18,25 @@ public:
|
|||
BandSplitterR() = default;
|
||||
BandSplitterR(const BandSplitterR&) = default;
|
||||
BandSplitterR(Real f0norm) { init(f0norm); }
|
||||
BandSplitterR& operator=(const BandSplitterR&) = default;
|
||||
|
||||
void init(Real f0norm);
|
||||
void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; }
|
||||
void process(const al::span<const Real> input, Real *hpout, Real *lpout);
|
||||
|
||||
void processHfScale(const al::span<Real> samples, const Real hfscale);
|
||||
void processHfScale(const al::span<const Real> input, Real *output, const Real hfscale);
|
||||
|
||||
/* The all-pass portion of the band splitter. Applies the same phase shift
|
||||
* without splitting the signal. Note that each use of this method is
|
||||
* indepedent, it does not track history between calls.
|
||||
void processHfScale(const al::span<Real> samples, const Real hfscale);
|
||||
void processScale(const al::span<Real> samples, const Real hfscale, const Real lfscale);
|
||||
|
||||
/**
|
||||
* The all-pass portion of the band splitter. Applies the same phase shift
|
||||
* without splitting the signal, in reverse. It starts from the back of the
|
||||
* span and works toward the front, creating a phase shift of -n degrees
|
||||
* instead of +n. Note that each use of this method is indepedent, it does
|
||||
* not track history between calls.
|
||||
*/
|
||||
void applyAllpass(const al::span<Real> samples) const;
|
||||
void applyAllpassRev(const al::span<Real> samples) const;
|
||||
};
|
||||
using BandSplitter = BandSplitterR<float>;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
#include <intrin.h>
|
||||
#endif
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#ifndef _MM_DENORMALS_ZERO_MASK
|
||||
/* Some headers seem to be missing these? */
|
||||
#define _MM_DENORMALS_ZERO_MASK 0x0040u
|
||||
#define _MM_DENORMALS_ZERO_ON 0x0040u
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "cpu_caps.h"
|
||||
|
|
@ -20,8 +25,8 @@ void FPUCtl::enter() noexcept
|
|||
#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 */
|
||||
sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK);
|
||||
sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON;
|
||||
_mm_setcsr(sseState);
|
||||
|
||||
#elif defined(__GNUC__) && defined(HAVE_SSE)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class FPUCtl {
|
|||
bool in_mode{};
|
||||
|
||||
public:
|
||||
FPUCtl() noexcept { enter(); in_mode = true; };
|
||||
FPUCtl() noexcept { enter(); in_mode = true; }
|
||||
~FPUCtl() { if(in_mode) leave(); }
|
||||
|
||||
FPUCtl(const FPUCtl&) = delete;
|
||||
|
|
|
|||
36
Engine/lib/openal-soft/core/front_stablizer.h
Normal file
36
Engine/lib/openal-soft/core/front_stablizer.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef CORE_FRONT_STABLIZER_H
|
||||
#define CORE_FRONT_STABLIZER_H
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "bufferline.h"
|
||||
#include "filters/splitter.h"
|
||||
|
||||
|
||||
struct FrontStablizer {
|
||||
static constexpr size_t DelayLength{256u};
|
||||
|
||||
FrontStablizer(size_t numchans) : DelayBuf{numchans} { }
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> Side{};
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> MidDirect{};
|
||||
alignas(16) std::array<float,DelayLength> MidDelay{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> TempBuf{};
|
||||
|
||||
BandSplitter MidFilter;
|
||||
alignas(16) FloatBufferLine MidLF{};
|
||||
alignas(16) FloatBufferLine MidHF{};
|
||||
|
||||
using DelayLine = std::array<float,DelayLength>;
|
||||
al::FlexArray<DelayLine,16> DelayBuf;
|
||||
|
||||
static std::unique_ptr<FrontStablizer> Create(size_t numchans)
|
||||
{ return std::unique_ptr<FrontStablizer>{new(FamCount(numchans)) FrontStablizer{numchans}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(FrontStablizer, DelayBuf)
|
||||
};
|
||||
|
||||
#endif /* CORE_FRONT_STABLIZER_H */
|
||||
557
Engine/lib/openal-soft/core/helpers.cpp
Normal file
557
Engine/lib/openal-soft/core/helpers.cpp
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alfstream.h"
|
||||
#include "alnumeric.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "alstring.h"
|
||||
#include "logging.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
/* Mixing thread piority level */
|
||||
int RTPrioLevel{1};
|
||||
|
||||
/* Allow reducing the process's RTTime limit for RTKit. */
|
||||
bool AllowRTTimeLimit{true};
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <shlobj.h>
|
||||
|
||||
const PathNamePair &GetProcBinary()
|
||||
{
|
||||
static al::optional<PathNamePair> procbin;
|
||||
if(procbin) return *procbin;
|
||||
|
||||
auto fullpath = al::vector<WCHAR>(256);
|
||||
DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))};
|
||||
while(len == fullpath.size())
|
||||
{
|
||||
fullpath.resize(fullpath.size() << 1);
|
||||
len = GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()));
|
||||
}
|
||||
if(len == 0)
|
||||
{
|
||||
ERR("Failed to get process name: error %lu\n", GetLastError());
|
||||
procbin = al::make_optional<PathNamePair>();
|
||||
return *procbin;
|
||||
}
|
||||
|
||||
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;
|
||||
procbin = al::make_optional<PathNamePair>(wstr_to_utf8(fullpath.data()),
|
||||
wstr_to_utf8(&*sep + 1));
|
||||
}
|
||||
else
|
||||
procbin = al::make_optional<PathNamePair>(std::string{}, wstr_to_utf8(fullpath.data()));
|
||||
|
||||
TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str());
|
||||
return *procbin;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
|
||||
{
|
||||
std::string pathstr{path};
|
||||
pathstr += "\\*";
|
||||
pathstr += ext;
|
||||
TRACE("Searching %s\n", pathstr.c_str());
|
||||
|
||||
std::wstring wpath{utf8_to_wstr(pathstr.c_str())};
|
||||
WIN32_FIND_DATAW fdata;
|
||||
HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)};
|
||||
if(hdl == INVALID_HANDLE_VALUE) return;
|
||||
|
||||
const auto base = results->size();
|
||||
|
||||
do {
|
||||
results->emplace_back();
|
||||
std::string &str = results->back();
|
||||
str = path;
|
||||
str += '\\';
|
||||
str += wstr_to_utf8(fdata.cFileName);
|
||||
} while(FindNextFileW(hdl, &fdata));
|
||||
FindClose(hdl);
|
||||
|
||||
const al::span<std::string> newlist{results->data()+base, results->size()-base};
|
||||
std::sort(newlist.begin(), newlist.end());
|
||||
for(const auto &name : newlist)
|
||||
TRACE(" got %s\n", name.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
||||
{
|
||||
auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); };
|
||||
|
||||
static std::mutex search_lock;
|
||||
std::lock_guard<std::mutex> _{search_lock};
|
||||
|
||||
/* If the path is absolute, use it directly. */
|
||||
al::vector<std::string> results;
|
||||
if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
|
||||
{
|
||||
std::string path{subdir};
|
||||
std::replace(path.begin(), path.end(), '/', '\\');
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
return results;
|
||||
}
|
||||
if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
|
||||
{
|
||||
DirectorySearch(subdir, ext, &results);
|
||||
return results;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
|
||||
/* Search the app-local directory. */
|
||||
if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
|
||||
{
|
||||
path = wstr_to_utf8(localpath->c_str());
|
||||
if(is_slash(path.back()))
|
||||
path.pop_back();
|
||||
}
|
||||
else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)})
|
||||
{
|
||||
path = wstr_to_utf8(cwdbuf);
|
||||
if(is_slash(path.back()))
|
||||
path.pop_back();
|
||||
free(cwdbuf);
|
||||
}
|
||||
else
|
||||
path = ".";
|
||||
std::replace(path.begin(), path.end(), '/', '\\');
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
|
||||
/* Search the local and global data dirs. */
|
||||
static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
|
||||
for(int id : ids)
|
||||
{
|
||||
WCHAR buffer[MAX_PATH];
|
||||
if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
|
||||
continue;
|
||||
|
||||
path = wstr_to_utf8(buffer);
|
||||
if(!is_slash(path.back()))
|
||||
path += '\\';
|
||||
path += subdir;
|
||||
std::replace(path.begin(), path.end(), '/', '\\');
|
||||
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void SetRTPriority(void)
|
||||
{
|
||||
if(RTPrioLevel > 0)
|
||||
{
|
||||
if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
|
||||
ERR("Failed to set priority level for thread\n");
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#ifdef __HAIKU__
|
||||
#include <FindDirectory.h>
|
||||
#endif
|
||||
#ifdef HAVE_PROC_PIDPATH
|
||||
#include <libproc.h>
|
||||
#endif
|
||||
#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#endif
|
||||
#ifdef HAVE_RTKIT
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
#include "rtkit.h"
|
||||
#ifndef RLIMIT_RTTIME
|
||||
#define RLIMIT_RTTIME 15
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const PathNamePair &GetProcBinary()
|
||||
{
|
||||
static al::optional<PathNamePair> procbin;
|
||||
if(procbin) return *procbin;
|
||||
|
||||
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
|
||||
#ifdef __HAIKU__
|
||||
if(pathname.empty())
|
||||
{
|
||||
char procpath[PATH_MAX];
|
||||
if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK)
|
||||
pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
|
||||
}
|
||||
#endif
|
||||
#ifndef __SWITCH__
|
||||
if(pathname.empty())
|
||||
{
|
||||
static const char SelfLinkNames[][32]{
|
||||
"/proc/self/exe",
|
||||
"/proc/self/file",
|
||||
"/proc/curproc/exe",
|
||||
"/proc/curproc/file"
|
||||
};
|
||||
|
||||
pathname.resize(256);
|
||||
|
||||
const char *selfname{};
|
||||
ssize_t len{};
|
||||
for(const char *name : SelfLinkNames)
|
||||
{
|
||||
selfname = name;
|
||||
len = readlink(selfname, pathname.data(), pathname.size());
|
||||
if(len >= 0 || errno != ENOENT) break;
|
||||
}
|
||||
|
||||
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));
|
||||
len = 0;
|
||||
}
|
||||
|
||||
pathname.resize(static_cast<size_t>(len));
|
||||
}
|
||||
#endif
|
||||
while(!pathname.empty() && pathname.back() == 0)
|
||||
pathname.pop_back();
|
||||
|
||||
auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
|
||||
if(sep != pathname.crend())
|
||||
procbin = al::make_optional<PathNamePair>(std::string(pathname.cbegin(), sep.base()-1),
|
||||
std::string(sep.base(), pathname.cend()));
|
||||
else
|
||||
procbin = al::make_optional<PathNamePair>(std::string{},
|
||||
std::string(pathname.cbegin(), pathname.cend()));
|
||||
|
||||
TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str());
|
||||
return *procbin;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
|
||||
{
|
||||
TRACE("Searching %s for *%s\n", path, ext);
|
||||
DIR *dir{opendir(path)};
|
||||
if(!dir) return;
|
||||
|
||||
const auto base = results->size();
|
||||
const size_t extlen{strlen(ext)};
|
||||
|
||||
while(struct dirent *dirent{readdir(dir)})
|
||||
{
|
||||
if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
|
||||
continue;
|
||||
|
||||
const size_t len{strlen(dirent->d_name)};
|
||||
if(len <= extlen) continue;
|
||||
if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0)
|
||||
continue;
|
||||
|
||||
results->emplace_back();
|
||||
std::string &str = results->back();
|
||||
str = path;
|
||||
if(str.back() != '/')
|
||||
str.push_back('/');
|
||||
str += dirent->d_name;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
const al::span<std::string> newlist{results->data()+base, results->size()-base};
|
||||
std::sort(newlist.begin(), newlist.end());
|
||||
for(const auto &name : newlist)
|
||||
TRACE(" got %s\n", name.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
||||
{
|
||||
static std::mutex search_lock;
|
||||
std::lock_guard<std::mutex> _{search_lock};
|
||||
|
||||
al::vector<std::string> results;
|
||||
if(subdir[0] == '/')
|
||||
{
|
||||
DirectorySearch(subdir, ext, &results);
|
||||
return results;
|
||||
}
|
||||
|
||||
/* Search the app-local directory. */
|
||||
if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
|
||||
DirectorySearch(localpath->c_str(), ext, &results);
|
||||
else
|
||||
{
|
||||
al::vector<char> cwdbuf(256);
|
||||
while(!getcwd(cwdbuf.data(), cwdbuf.size()))
|
||||
{
|
||||
if(errno != ERANGE)
|
||||
{
|
||||
cwdbuf.clear();
|
||||
break;
|
||||
}
|
||||
cwdbuf.resize(cwdbuf.size() << 1);
|
||||
}
|
||||
if(cwdbuf.empty())
|
||||
DirectorySearch(".", ext, &results);
|
||||
else
|
||||
{
|
||||
DirectorySearch(cwdbuf.data(), ext, &results);
|
||||
cwdbuf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Search local data dir
|
||||
if(auto datapath = al::getenv("XDG_DATA_HOME"))
|
||||
{
|
||||
std::string &path = *datapath;
|
||||
if(path.back() != '/')
|
||||
path += '/';
|
||||
path += subdir;
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
else if(auto homepath = al::getenv("HOME"))
|
||||
{
|
||||
std::string &path = *homepath;
|
||||
if(path.back() == '/')
|
||||
path.pop_back();
|
||||
path += "/.local/share/";
|
||||
path += subdir;
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
|
||||
// Search global data dirs
|
||||
std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
|
||||
|
||||
size_t curpos{0u};
|
||||
while(curpos < datadirs.size())
|
||||
{
|
||||
size_t nextpos{datadirs.find(':', curpos)};
|
||||
|
||||
std::string path{(nextpos != std::string::npos) ?
|
||||
datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)};
|
||||
curpos = nextpos;
|
||||
|
||||
if(path.empty()) continue;
|
||||
if(path.back() != '/')
|
||||
path += '/';
|
||||
path += subdir;
|
||||
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool SetRTPriorityPthread(int prio)
|
||||
{
|
||||
int err{ENOTSUP};
|
||||
#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
|
||||
/* Get the min and max priority for SCHED_RR. Limit the max priority to
|
||||
* half, for now, to ensure the thread can't take the highest priority and
|
||||
* go rogue.
|
||||
*/
|
||||
int rtmin{sched_get_priority_min(SCHED_RR)};
|
||||
int rtmax{sched_get_priority_max(SCHED_RR)};
|
||||
rtmax = (rtmax-rtmin)/2 + rtmin;
|
||||
|
||||
struct sched_param param{};
|
||||
param.sched_priority = clampi(prio, rtmin, rtmax);
|
||||
#ifdef SCHED_RESET_ON_FORK
|
||||
err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m);
|
||||
if(err == EINVAL)
|
||||
#endif
|
||||
err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m);
|
||||
if(err == 0) return true;
|
||||
|
||||
#else
|
||||
|
||||
std::ignore = prio;
|
||||
#endif
|
||||
WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetRTPriorityRTKit(int prio)
|
||||
{
|
||||
#ifdef HAVE_RTKIT
|
||||
if(!HasDBus())
|
||||
{
|
||||
WARN("D-Bus not available\n");
|
||||
return false;
|
||||
}
|
||||
dbus::Error error;
|
||||
dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())};
|
||||
if(!conn)
|
||||
{
|
||||
WARN("D-Bus connection failed with %s: %s\n", error->name, error->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't stupidly exit if the connection dies while doing this. */
|
||||
dbus_connection_set_exit_on_disconnect(conn.get(), false);
|
||||
|
||||
auto limit_rttime = [](DBusConnection *c) -> int
|
||||
{
|
||||
using ulonglong = unsigned long long;
|
||||
long long maxrttime{rtkit_get_rttime_usec_max(c)};
|
||||
if(maxrttime <= 0) return static_cast<int>(std::abs(maxrttime));
|
||||
const ulonglong umaxtime{static_cast<ulonglong>(maxrttime)};
|
||||
|
||||
struct rlimit rlim{};
|
||||
if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
|
||||
TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, ulonglong{rlim.rlim_max},
|
||||
ulonglong{rlim.rlim_cur});
|
||||
if(rlim.rlim_max > umaxtime)
|
||||
{
|
||||
rlim.rlim_max = static_cast<rlim_t>(umaxtime);
|
||||
rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
|
||||
if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
int nicemin{};
|
||||
int err{rtkit_get_min_nice_level(conn.get(), &nicemin)};
|
||||
if(err == -ENOENT)
|
||||
{
|
||||
err = std::abs(err);
|
||||
ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err);
|
||||
return false;
|
||||
}
|
||||
int rtmax{rtkit_get_max_realtime_priority(conn.get())};
|
||||
TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin);
|
||||
|
||||
if(rtmax > 0)
|
||||
{
|
||||
if(AllowRTTimeLimit)
|
||||
{
|
||||
err = limit_rttime(conn.get());
|
||||
if(err != 0)
|
||||
WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
|
||||
std::strerror(err), err);
|
||||
}
|
||||
|
||||
/* Limit the maximum real-time priority to half. */
|
||||
rtmax = (rtmax+1)/2;
|
||||
prio = clampi(prio, 1, rtmax);
|
||||
|
||||
TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax);
|
||||
err = rtkit_make_realtime(conn.get(), 0, prio);
|
||||
if(err == 0) return true;
|
||||
|
||||
err = std::abs(err);
|
||||
WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err);
|
||||
}
|
||||
/* Don't try to set the niceness for non-Linux systems. Standard POSIX has
|
||||
* niceness as a per-process attribute, while the intent here is for the
|
||||
* audio processing thread only to get a priority boost. Currently only
|
||||
* Linux is known to have per-thread niceness.
|
||||
*/
|
||||
#ifdef __linux__
|
||||
if(nicemin < 0)
|
||||
{
|
||||
TRACE("Making high priority with niceness %d\n", nicemin);
|
||||
err = rtkit_make_high_priority(conn.get(), 0, nicemin);
|
||||
if(err == 0) return true;
|
||||
|
||||
err = std::abs(err);
|
||||
WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err);
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
|
||||
#else
|
||||
|
||||
std::ignore = prio;
|
||||
WARN("D-Bus not supported\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetRTPriority()
|
||||
{
|
||||
if(RTPrioLevel <= 0)
|
||||
return;
|
||||
|
||||
if(SetRTPriorityPthread(RTPrioLevel))
|
||||
return;
|
||||
if(SetRTPriorityRTKit(RTPrioLevel))
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
18
Engine/lib/openal-soft/core/helpers.h
Normal file
18
Engine/lib/openal-soft/core/helpers.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef CORE_HELPERS_H
|
||||
#define CORE_HELPERS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
struct PathNamePair { std::string path, fname; };
|
||||
const PathNamePair &GetProcBinary(void);
|
||||
|
||||
extern int RTPrioLevel;
|
||||
extern bool AllowRTTimeLimit;
|
||||
void SetRTPriority(void);
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
|
||||
|
||||
#endif /* CORE_HELPERS_H */
|
||||
1462
Engine/lib/openal-soft/core/hrtf.cpp
Normal file
1462
Engine/lib/openal-soft/core/hrtf.cpp
Normal file
File diff suppressed because it is too large
Load diff
90
Engine/lib/openal-soft/core/hrtf.h
Normal file
90
Engine/lib/openal-soft/core/hrtf.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef CORE_HRTF_H
|
||||
#define CORE_HRTF_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
struct HrtfStore {
|
||||
RefCount mRef;
|
||||
|
||||
uint sampleRate;
|
||||
uint irSize;
|
||||
|
||||
struct Field {
|
||||
float distance;
|
||||
ubyte evCount;
|
||||
};
|
||||
/* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and
|
||||
* field[fdCount-1] is the nearest.
|
||||
*/
|
||||
uint fdCount;
|
||||
const Field *field;
|
||||
|
||||
struct Elevation {
|
||||
ushort azCount;
|
||||
ushort irOffset;
|
||||
};
|
||||
Elevation *elev;
|
||||
const HrirArray *coeffs;
|
||||
const ubyte2 *delays;
|
||||
|
||||
void add_ref();
|
||||
void release();
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
};
|
||||
using HrtfStorePtr = al::intrusive_ptr<HrtfStore>;
|
||||
|
||||
|
||||
struct EvRadians { float value; };
|
||||
struct AzRadians { float value; };
|
||||
struct AngularPoint {
|
||||
EvRadians Elev;
|
||||
AzRadians Azim;
|
||||
};
|
||||
|
||||
|
||||
struct DirectHrtfState {
|
||||
std::array<float,BufferLineSize> mTemp;
|
||||
|
||||
/* HRTF filter state for dry buffer content */
|
||||
uint mIrSize{0};
|
||||
al::FlexArray<HrtfChannelState> mChannels;
|
||||
|
||||
DirectHrtfState(size_t numchans) : mChannels{numchans} { }
|
||||
/**
|
||||
* Produces HRTF filter coefficients for decoding B-Format, given a set of
|
||||
* virtual speaker positions, a matching decoding matrix, and per-order
|
||||
* high-frequency gains for the decoder. The calculated impulse responses
|
||||
* are ordered and scaled according to the matrix input.
|
||||
*/
|
||||
void build(const HrtfStore *Hrtf, const uint irSize,
|
||||
const al::span<const AngularPoint> AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels],
|
||||
const float XOverFreq, const al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain);
|
||||
|
||||
static std::unique_ptr<DirectHrtfState> Create(size_t num_chans);
|
||||
|
||||
DEF_FAM_NEWDEL(DirectHrtfState, mChannels)
|
||||
};
|
||||
|
||||
|
||||
al::vector<std::string> EnumerateHrtf(al::optional<std::string> pathopt);
|
||||
HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate);
|
||||
|
||||
void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance,
|
||||
float spread, HrirArray &coeffs, const al::span<uint,2> delays);
|
||||
|
||||
#endif /* CORE_HRTF_H */
|
||||
|
|
@ -25,23 +25,29 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
|||
std::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))
|
||||
const 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);
|
||||
std::vsnprintf(str, dynmsg.size(), fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
||||
std::wstring wstr{utf8_to_wstr(str)};
|
||||
if(gLogLevel >= level)
|
||||
{
|
||||
fputws(wstr.c_str(), logfile);
|
||||
fputs(str, logfile);
|
||||
fflush(logfile);
|
||||
}
|
||||
/* OutputDebugStringW has no 'level' property to distinguish between
|
||||
* informational, warning, or error debug messages. So only print them for
|
||||
* non-Release builds.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
std::wstring wstr{utf8_to_wstr(str)};
|
||||
OutputDebugStringW(wstr.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
@ -59,12 +65,12 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
|||
std::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))
|
||||
const 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);
|
||||
std::vsnprintf(str, dynmsg.size(), fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ extern FILE *gLogFile;
|
|||
|
||||
#else
|
||||
|
||||
[[gnu::format(printf,3,4)]] void al_print(LogLevel level, FILE *logfile, const char *fmt, ...);
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,3,4)]]
|
||||
#else
|
||||
[[gnu::format(printf,3,4)]]
|
||||
#endif
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...);
|
||||
|
||||
#define TRACE(...) al_print(LogLevel::Trace, gLogFile, "[ALSOFT] (II) " __VA_ARGS__)
|
||||
|
||||
|
|
|
|||
|
|
@ -133,8 +133,8 @@ static void CrestDetector(Compressor *Comp, const uint SamplesToDo)
|
|||
{
|
||||
const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)};
|
||||
|
||||
y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest));
|
||||
y2_rms = lerp(x2, y2_rms, a_crest);
|
||||
y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest));
|
||||
y2_rms = lerpf(x2, y2_rms, a_crest);
|
||||
return y2_peak / y2_rms;
|
||||
};
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
|
|
@ -243,15 +243,15 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
|||
* above to compensate for the chained operating mode.
|
||||
*/
|
||||
const float x_L{-slope * y_G};
|
||||
y_1 = maxf(x_L, lerp(x_L, y_1, a_rel));
|
||||
y_L = lerp(y_1, y_L, a_att);
|
||||
y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel));
|
||||
y_L = lerpf(y_1, y_L, a_att);
|
||||
|
||||
/* Knee width and make-up gain automation make use of a smoothed
|
||||
* measurement of deviation between the control signal and estimate.
|
||||
* The estimate is also used to bias the measurement to hot-start its
|
||||
* average.
|
||||
*/
|
||||
c_dev = lerp(-(y_L+c_est), c_dev, a_adp);
|
||||
c_dev = lerpf(-(y_L+c_est), c_dev, a_adp);
|
||||
|
||||
if(autoPostGain)
|
||||
{
|
||||
|
|
@ -334,7 +334,7 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
size += sizeof(*Compressor::mHold);
|
||||
}
|
||||
|
||||
auto Comp = std::unique_ptr<Compressor>{new (al_calloc(16, size)) Compressor{}};
|
||||
auto Comp = CompressorPtr{al::construct_at(static_cast<Compressor*>(al_calloc(16, size)))};
|
||||
Comp->mNumChans = NumChans;
|
||||
Comp->mAuto.Knee = AutoKnee;
|
||||
Comp->mAuto.Attack = AutoAttack;
|
||||
|
|
@ -361,17 +361,15 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
{
|
||||
if(hold > 1)
|
||||
{
|
||||
Comp->mHold = ::new (static_cast<void*>(Comp.get() + 1)) SlidingHold{};
|
||||
Comp->mHold = al::construct_at(reinterpret_cast<SlidingHold*>(Comp.get() + 1));
|
||||
Comp->mHold->mValues[0] = -std::numeric_limits<float>::infinity();
|
||||
Comp->mHold->mExpiries[0] = hold;
|
||||
Comp->mHold->mLength = hold;
|
||||
Comp->mDelay = ::new(static_cast<void*>(Comp->mHold + 1)) FloatBufferLine[NumChans];
|
||||
Comp->mDelay = reinterpret_cast<FloatBufferLine*>(Comp->mHold + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Comp->mDelay = ::new(static_cast<void*>(Comp.get() + 1)) FloatBufferLine[NumChans];
|
||||
}
|
||||
std::fill_n(Comp->mDelay, NumChans, FloatBufferLine{});
|
||||
Comp->mDelay = reinterpret_cast<FloatBufferLine*>(Comp.get() + 1);
|
||||
std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{});
|
||||
}
|
||||
|
||||
Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms
|
||||
|
|
|
|||
|
|
@ -100,5 +100,6 @@ struct Compressor {
|
|||
const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime,
|
||||
const float ReleaseTime);
|
||||
};
|
||||
using CompressorPtr = std::unique_ptr<Compressor>;
|
||||
|
||||
#endif /* CORE_MASTERING_H */
|
||||
|
|
|
|||
126
Engine/lib/openal-soft/core/mixer.cpp
Normal file
126
Engine/lib/openal-soft/core/mixer.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "mixer.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "devformat.h"
|
||||
#include "device.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
struct CTag;
|
||||
|
||||
|
||||
MixerFunc MixSamples{Mix_<CTag>};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
|
||||
const float spread)
|
||||
{
|
||||
std::array<float,MaxAmbiChannels> coeffs;
|
||||
|
||||
/* Zeroth-order */
|
||||
coeffs[0] = 1.0f; /* ACN 0 = 1 */
|
||||
/* First-order */
|
||||
coeffs[1] = al::numbers::sqrt3_v<float> * y; /* ACN 1 = sqrt(3) * Y */
|
||||
coeffs[2] = al::numbers::sqrt3_v<float> * z; /* ACN 2 = sqrt(3) * Z */
|
||||
coeffs[3] = al::numbers::sqrt3_v<float> * x; /* ACN 3 = sqrt(3) * X */
|
||||
/* Second-order */
|
||||
const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z};
|
||||
coeffs[4] = 3.872983346f * xy; /* ACN 4 = sqrt(15) * X * Y */
|
||||
coeffs[5] = 3.872983346f * yz; /* ACN 5 = sqrt(15) * Y * Z */
|
||||
coeffs[6] = 1.118033989f * (3.0f*zz - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
|
||||
coeffs[7] = 3.872983346f * xz; /* ACN 7 = sqrt(15) * X * Z */
|
||||
coeffs[8] = 1.936491673f * (xx - yy); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
|
||||
/* Third-order */
|
||||
coeffs[9] = 2.091650066f * (y*(3.0f*xx - yy)); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
|
||||
coeffs[10] = 10.246950766f * (z*xy); /* ACN 10 = sqrt(105) * Z * X * Y */
|
||||
coeffs[11] = 1.620185175f * (y*(5.0f*zz - 1.0f)); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
|
||||
coeffs[12] = 1.322875656f * (z*(5.0f*zz - 3.0f)); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
|
||||
coeffs[13] = 1.620185175f * (x*(5.0f*zz - 1.0f)); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
|
||||
coeffs[14] = 5.123475383f * (z*(xx - yy)); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
|
||||
coeffs[15] = 2.091650066f * (x*(xx - 3.0f*yy)); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
|
||||
/* Fourth-order */
|
||||
/* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */
|
||||
/* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */
|
||||
/* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */
|
||||
/* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */
|
||||
/* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */
|
||||
/* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */
|
||||
/* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */
|
||||
/* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */
|
||||
/* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */
|
||||
|
||||
if(spread > 0.0f)
|
||||
{
|
||||
/* Implement the spread by using a spherical source that subtends the
|
||||
* angle spread. See:
|
||||
* http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3
|
||||
*
|
||||
* When adjusted for N3D normalization instead of SN3D, these
|
||||
* calculations are:
|
||||
*
|
||||
* ZH0 = -sqrt(pi) * (-1+ca);
|
||||
* ZH1 = 0.5*sqrt(pi) * sa*sa;
|
||||
* ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1);
|
||||
* ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1);
|
||||
* ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3);
|
||||
* ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1);
|
||||
*
|
||||
* The gain of the source is compensated for size, so that the
|
||||
* loudness doesn't depend on the spread. Thus:
|
||||
*
|
||||
* ZH0 = 1.0f;
|
||||
* ZH1 = 0.5f * (ca+1.0f);
|
||||
* ZH2 = 0.5f * (ca+1.0f)*ca;
|
||||
* ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f);
|
||||
* ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca;
|
||||
* ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f);
|
||||
*/
|
||||
const float ca{std::cos(spread * 0.5f)};
|
||||
/* Increase the source volume by up to +3dB for a full spread. */
|
||||
const float scale{std::sqrt(1.0f + al::numbers::inv_pi_v<float>/2.0f*spread)};
|
||||
|
||||
const float ZH0_norm{scale};
|
||||
const float ZH1_norm{scale * 0.5f * (ca+1.f)};
|
||||
const float ZH2_norm{scale * 0.5f * (ca+1.f)*ca};
|
||||
const float ZH3_norm{scale * 0.125f * (ca+1.f)*(5.f*ca*ca-1.f)};
|
||||
|
||||
/* Zeroth-order */
|
||||
coeffs[0] *= ZH0_norm;
|
||||
/* First-order */
|
||||
coeffs[1] *= ZH1_norm;
|
||||
coeffs[2] *= ZH1_norm;
|
||||
coeffs[3] *= ZH1_norm;
|
||||
/* Second-order */
|
||||
coeffs[4] *= ZH2_norm;
|
||||
coeffs[5] *= ZH2_norm;
|
||||
coeffs[6] *= ZH2_norm;
|
||||
coeffs[7] *= ZH2_norm;
|
||||
coeffs[8] *= ZH2_norm;
|
||||
/* Third-order */
|
||||
coeffs[9] *= ZH3_norm;
|
||||
coeffs[10] *= ZH3_norm;
|
||||
coeffs[11] *= ZH3_norm;
|
||||
coeffs[12] *= ZH3_norm;
|
||||
coeffs[13] *= ZH3_norm;
|
||||
coeffs[14] *= ZH3_norm;
|
||||
coeffs[15] *= ZH3_norm;
|
||||
}
|
||||
|
||||
return coeffs;
|
||||
}
|
||||
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains)
|
||||
{
|
||||
auto ambimap = mix->AmbiMap.cbegin();
|
||||
|
||||
auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(),
|
||||
[coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float
|
||||
{ return chanmap.Scale * coeffs[chanmap.Index] * ingain; }
|
||||
);
|
||||
std::fill(iter, gains.end(), 0.0f);
|
||||
}
|
||||
101
Engine/lib/openal-soft/core/mixer.h
Normal file
101
Engine/lib/openal-soft/core/mixer.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#ifndef CORE_MIXER_H
|
||||
#define CORE_MIXER_H
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <stddef.h>
|
||||
#include <type_traits>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
|
||||
struct MixParams;
|
||||
|
||||
using MixerFunc = void(*)(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos);
|
||||
|
||||
extern MixerFunc MixSamples;
|
||||
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector, and the
|
||||
* spread is the angular width of the sound (0...tau).
|
||||
*
|
||||
* NOTE: The components use ambisonic coordinates. As a result:
|
||||
*
|
||||
* Ambisonic Y = OpenAL -X
|
||||
* Ambisonic Z = OpenAL Y
|
||||
* Ambisonic X = OpenAL -Z
|
||||
*
|
||||
* The components are ordered such that OpenAL's X, Y, and Z are the first,
|
||||
* second, and third parameters respectively -- simply negate X and Z.
|
||||
*/
|
||||
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
|
||||
const float spread);
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length), and the spread is the angular width
|
||||
* of the sound (0...tau).
|
||||
*/
|
||||
inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[3],
|
||||
const float spread)
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcAngleCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on azimuth and elevation. The
|
||||
* azimuth and elevation parameters are in radians, going right and up
|
||||
* respectively.
|
||||
*/
|
||||
inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth,
|
||||
const float elevation, const float spread)
|
||||
{
|
||||
const float x{-std::sin(azimuth) * std::cos(elevation)};
|
||||
const float y{ std::sin(elevation)};
|
||||
const float z{ std::cos(azimuth) * std::cos(elevation)};
|
||||
|
||||
return CalcAmbiCoeffs(x, y, z, spread);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ComputePanGains
|
||||
*
|
||||
* Computes panning gains using the given channel decoder coefficients and the
|
||||
* pre-calculated direction or angle coefficients. For B-Format sources, the
|
||||
* coeffs are a 'slice' of a transform matrix for the input channel, used to
|
||||
* scale and orient the sound samples.
|
||||
*/
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains);
|
||||
|
||||
|
||||
/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */
|
||||
template<typename T, typename I, typename F>
|
||||
auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t<std::is_integral<I>::value>
|
||||
{
|
||||
if(count < 1) return;
|
||||
|
||||
std::array<float,MaxAmbiChannels> coeffs{{1.0f}};
|
||||
func(*iter, coeffs);
|
||||
++iter;
|
||||
for(I i{1};i < count;++i,++iter)
|
||||
{
|
||||
coeffs[i-1] = 0.0f;
|
||||
coeffs[i ] = 1.0f;
|
||||
func(*iter, coeffs);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CORE_MIXER_H */
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/resampler_limits.h"
|
||||
|
||||
struct HrtfChannelState;
|
||||
struct HrtfFilter;
|
||||
|
|
@ -19,12 +20,6 @@ constexpr int MixerFracBits{12};
|
|||
constexpr int MixerFracOne{1 << MixerFracBits};
|
||||
constexpr int MixerFracMask{MixerFracOne - 1};
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
|
||||
constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
|
||||
|
||||
|
|
@ -80,7 +75,7 @@ template<typename InstTag>
|
|||
void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize);
|
||||
template<typename InstTag>
|
||||
void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
|
||||
float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
using uint = unsigned int;
|
||||
|
||||
using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize,
|
||||
const HrirArray &Coeffs, const float left, const float right);
|
||||
const ConstHrirSpan Coeffs, const float left, const float right);
|
||||
|
||||
template<ApplyCoeffsT ApplyCoeffs>
|
||||
inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize,
|
||||
|
|
@ -20,7 +20,7 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c
|
|||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
const HrirArray &Coeffs = *hrtfparams->Coeffs;
|
||||
const ConstHrirSpan Coeffs{hrtfparams->Coeffs};
|
||||
const float gainstep{hrtfparams->GainStep};
|
||||
const float gain{hrtfparams->Gain};
|
||||
|
||||
|
|
@ -45,9 +45,9 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
const auto &OldCoeffs = oldparams->Coeffs;
|
||||
const ConstHrirSpan OldCoeffs{oldparams->Coeffs};
|
||||
const float oldGainStep{oldparams->Gain / static_cast<float>(BufferSize)};
|
||||
const auto &NewCoeffs = *newparams->Coeffs;
|
||||
const ConstHrirSpan NewCoeffs{newparams->Coeffs};
|
||||
const float newGainStep{newparams->GainStep};
|
||||
|
||||
if LIKELY(oldparams->Gain > GainSilenceThreshold)
|
||||
|
|
@ -84,56 +84,24 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
}
|
||||
|
||||
template<ApplyCoeffsT ApplyCoeffs>
|
||||
inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *RESTRICT AccumSamples,
|
||||
float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
/* Add the existing signal directly to the accumulation buffer, unfiltered,
|
||||
* and with a delay to align with the input delay.
|
||||
*/
|
||||
for(size_t i{0};i < BufferSize;++i)
|
||||
{
|
||||
AccumSamples[HrtfDirectDelay+i][0] += LeftOut[i];
|
||||
AccumSamples[HrtfDirectDelay+i][1] += RightOut[i];
|
||||
}
|
||||
|
||||
for(const FloatBufferLine &input : InSamples)
|
||||
{
|
||||
/* For dual-band processing, the signal needs extra scaling applied to
|
||||
* the high frequency response. The band-splitter alone creates a
|
||||
* frequency-dependent phase shift, which is not ideal. To counteract
|
||||
* it, combine it with a backwards phase shift.
|
||||
* the high frequency response. The band-splitter applies this scaling
|
||||
* with a consistent phase shift regardless of the scale amount.
|
||||
*/
|
||||
|
||||
/* Load the input signal backwards, into a temp buffer with delay
|
||||
* padding. The delay serves to reduce the error caused by the IIR
|
||||
* filter's phase shift on a partial input.
|
||||
*/
|
||||
al::span<float> tempbuf{al::assume_aligned<16>(TempBuf), HrtfDirectDelay+BufferSize};
|
||||
auto tmpiter = std::reverse_copy(input.begin(), input.begin()+BufferSize, tempbuf.begin());
|
||||
std::copy(ChanState->mDelay.cbegin(), ChanState->mDelay.cend(), tmpiter);
|
||||
|
||||
/* Save the unfiltered newest input samples for next time. */
|
||||
std::copy_n(tempbuf.begin(), ChanState->mDelay.size(), ChanState->mDelay.begin());
|
||||
|
||||
/* Apply the all-pass on the reversed signal and reverse the resulting
|
||||
* sample array. This produces the forward response with a backwards
|
||||
* phase shift (+n degrees becomes -n degrees).
|
||||
*/
|
||||
ChanState->mSplitter.applyAllpass(tempbuf);
|
||||
tempbuf = tempbuf.subspan<HrtfDirectDelay>();
|
||||
std::reverse(tempbuf.begin(), tempbuf.end());
|
||||
|
||||
/* Now apply the HF scale with the band-splitter. This applies the
|
||||
* forward phase shift, which cancels out with the backwards phase
|
||||
* shift to get the original phase on the scaled signal.
|
||||
*/
|
||||
ChanState->mSplitter.processHfScale(tempbuf, ChanState->mHfScale);
|
||||
ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf,
|
||||
ChanState->mHfScale);
|
||||
|
||||
/* Now apply the HRIR coefficients to this channel. */
|
||||
const auto &Coeffs = ChanState->mCoeffs;
|
||||
const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)};
|
||||
const ConstHrirSpan Coeffs{ChanState->mCoeffs};
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
{
|
||||
const float insample{tempbuf[i]};
|
||||
|
|
@ -143,16 +111,18 @@ inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOu
|
|||
++ChanState;
|
||||
}
|
||||
|
||||
/* Add the HRTF signal to the existing "direct" signal. */
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())};
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut.data())};
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
LeftOut[i] = AccumSamples[i][0];
|
||||
left[i] += AccumSamples[i][0];
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
RightOut[i] = AccumSamples[i][1];
|
||||
right[i] += AccumSamples[i][1];
|
||||
|
||||
/* Copy the new in-progress accumulation values to the front and clear the
|
||||
* following samples for the next mix.
|
||||
*/
|
||||
auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength+HrtfDirectDelay,
|
||||
AccumSamples);
|
||||
auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength, AccumSamples);
|
||||
std::fill_n(accum_iter, BufferSize, float2{});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <array>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/filters/splitter.h"
|
||||
|
|
@ -25,12 +26,12 @@ constexpr uint HrirMask{HrirLength - 1};
|
|||
|
||||
constexpr uint MinIrLength{8};
|
||||
|
||||
constexpr uint HrtfDirectDelay{256};
|
||||
|
||||
using HrirArray = std::array<float2,HrirLength>;
|
||||
using HrirSpan = al::span<float2,HrirLength>;
|
||||
using ConstHrirSpan = al::span<const float2,HrirLength>;
|
||||
|
||||
struct MixHrtfFilter {
|
||||
const HrirArray *Coeffs;
|
||||
const ConstHrirSpan Coeffs;
|
||||
uint2 Delay;
|
||||
float Gain;
|
||||
float GainStep;
|
||||
|
|
@ -44,7 +45,6 @@ struct HrtfFilter {
|
|||
|
||||
|
||||
struct HrtfChannelState {
|
||||
std::array<float,HrtfDirectDelay> mDelay{};
|
||||
BandSplitter mSplitter;
|
||||
float mHfScale{};
|
||||
alignas(16) HrirArray mCoeffs{};
|
||||
|
|
|
|||
|
|
@ -26,21 +26,22 @@ constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
|||
inline float do_point(const InterpState&, const float *RESTRICT vals, const uint)
|
||||
{ return vals[0]; }
|
||||
inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac)
|
||||
{ return lerp(vals[0], vals[1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
{ return lerpf(vals[0], vals[1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint frac)
|
||||
{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
{
|
||||
const size_t m{istate.bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
|
||||
const float *fil{istate.bsinc.filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *scd{phd + m};
|
||||
const float *spd{scd + m};
|
||||
const float *RESTRICT fil{istate.bsinc.filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
const float *RESTRICT scd{fil + BSincPhaseCount*2*m};
|
||||
const float *RESTRICT spd{scd + m};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
float r{0.0f};
|
||||
|
|
@ -51,13 +52,14 @@ inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, con
|
|||
inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
{
|
||||
const size_t m{istate.bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
|
||||
const float *fil{istate.bsinc.filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *RESTRICT fil{istate.bsinc.filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
float r{0.0f};
|
||||
|
|
@ -83,7 +85,7 @@ float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint
|
|||
return dst.data();
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const HrirArray &Coeffs,
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
ASSUME(IrSize >= MinIrLength);
|
||||
|
|
@ -149,7 +151,7 @@ void MixHrtfBlend_<CTag>(const float *InSamples, float2 *AccumSamples, const uin
|
|||
}
|
||||
|
||||
template<>
|
||||
void MixDirectHrtf_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
void MixDirectHrtf_<CTag>(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
|
||||
float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
|||
constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const HrirArray &Coeffs,
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
float32x4_t leftright4;
|
||||
|
|
@ -101,7 +101,7 @@ float *Resample_<LerpTag,NEONTag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac = static_cast<uint>(vgetq_lane_s32(frac4, 0));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
|
|
@ -118,6 +118,7 @@ float *Resample_<BSincTag,NEONTag>(const InterpState *state, float *RESTRICT src
|
|||
const float *const filter{state->bsinc.filter};
|
||||
const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
|
|
@ -130,10 +131,10 @@ float *Resample_<BSincTag,NEONTag>(const InterpState *state, float *RESTRICT src
|
|||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
{
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
const float *fil{filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *scd{phd + m};
|
||||
const float *spd{scd + m};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
const float *RESTRICT scd{fil + BSincPhaseCount*2*m};
|
||||
const float *RESTRICT spd{scd + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -163,6 +164,7 @@ float *Resample_<FastBSincTag,NEONTag>(const InterpState *state, float *RESTRICT
|
|||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
|
|
@ -175,8 +177,8 @@ float *Resample_<FastBSincTag,NEONTag>(const InterpState *state, float *RESTRICT
|
|||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
{
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
const float *fil{filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -213,7 +215,7 @@ void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const
|
|||
}
|
||||
|
||||
template<>
|
||||
void MixDirectHrtf_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
void MixDirectHrtf_<NEONTag>(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
|
||||
float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize)
|
||||
{
|
||||
|
|
@ -243,7 +245,7 @@ void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBu
|
|||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{(min_len-pos) >> 2})
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const float32x4_t four4{vdupq_n_f32(4.0f)};
|
||||
const float32x4_t step4{vdupq_n_f32(step)};
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ struct BSincTag;
|
|||
struct FastBSincTag;
|
||||
|
||||
|
||||
/* SSE2 is required for any SSE support. */
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__)
|
||||
#pragma GCC target("sse2")
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__)
|
||||
#pragma GCC target("sse")
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
|
@ -27,7 +26,7 @@ constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
|||
|
||||
#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const HrirArray &Coeffs,
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
const __m128 lrlr{_mm_setr_ps(left, right, left, right)};
|
||||
|
|
@ -37,7 +36,17 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Hrir
|
|||
* systems that support SSE, which is the only one that needs to know the
|
||||
* alignment of Values (which alternates between 8- and 16-byte aligned).
|
||||
*/
|
||||
if(reinterpret_cast<intptr_t>(Values)&0x8)
|
||||
if(!(reinterpret_cast<uintptr_t>(Values)&15))
|
||||
{
|
||||
for(size_t i{0};i < IrSize;i += 2)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])};
|
||||
__m128 vals{_mm_load_ps(&Values[i][0])};
|
||||
vals = MLA4(vals, lrlr, coeffs);
|
||||
_mm_store_ps(&Values[i][0], vals);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__m128 imp0, imp1;
|
||||
__m128 coeffs{_mm_load_ps(&Coeffs[0][0])};
|
||||
|
|
@ -62,16 +71,6 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Hrir
|
|||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t i{0};i < IrSize;i += 2)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])};
|
||||
__m128 vals{_mm_load_ps(&Values[i][0])};
|
||||
vals = MLA4(vals, lrlr, coeffs);
|
||||
_mm_store_ps(&Values[i][0], vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -83,6 +82,7 @@ float *Resample_<BSincTag,SSETag>(const InterpState *state, float *RESTRICT src,
|
|||
const float *const filter{state->bsinc.filter};
|
||||
const __m128 sf4{_mm_set1_ps(state->bsinc.sf)};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
|
|
@ -95,10 +95,10 @@ float *Resample_<BSincTag,SSETag>(const InterpState *state, float *RESTRICT src,
|
|||
__m128 r4{_mm_setzero_ps()};
|
||||
{
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
const float *fil{filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *scd{phd + m};
|
||||
const float *spd{scd + m};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
const float *RESTRICT scd{fil + BSincPhaseCount*2*m};
|
||||
const float *RESTRICT spd{scd + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -129,6 +129,7 @@ float *Resample_<FastBSincTag,SSETag>(const InterpState *state, float *RESTRICT
|
|||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
|
|
@ -141,8 +142,8 @@ float *Resample_<FastBSincTag,SSETag>(const InterpState *state, float *RESTRICT
|
|||
__m128 r4{_mm_setzero_ps()};
|
||||
{
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
const float *fil{filter + m*pi*4};
|
||||
const float *phd{fil + m};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -180,7 +181,7 @@ void MixHrtfBlend_<SSETag>(const float *InSamples, float2 *AccumSamples, const u
|
|||
}
|
||||
|
||||
template<>
|
||||
void MixDirectHrtf_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
void MixDirectHrtf_<SSETag>(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
||||
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
|
||||
float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize)
|
||||
{
|
||||
|
|
@ -210,7 +211,7 @@ void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBuf
|
|||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{(min_len-pos) >> 2})
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const __m128 four4{_mm_set1_ps(4.0f)};
|
||||
const __m128 step4{_mm_set1_ps(step)};
|
||||
|
|
|
|||
|
|
@ -52,10 +52,10 @@ float *Resample_<LerpTag,SSE2Tag>(const InterpState*, float *RESTRICT src, uint
|
|||
auto dst_iter = dst.begin();
|
||||
for(size_t todo{dst.size()>>2};todo;--todo)
|
||||
{
|
||||
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 int pos0{_mm_cvtsi128_si32(pos4)};
|
||||
const int pos1{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))};
|
||||
const int pos2{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))};
|
||||
const int pos3{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))};
|
||||
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])};
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ float *Resample_<LerpTag,SSE2Tag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ float *Resample_<LerpTag,SSE4Tag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
|
|
|
|||
12
Engine/lib/openal-soft/core/resampler_limits.h
Normal file
12
Engine/lib/openal-soft/core/resampler_limits.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef CORE_RESAMPLER_LIMITS_H
|
||||
#define CORE_RESAMPLER_LIMITS_H
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
|
||||
constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1};
|
||||
|
||||
#endif /* CORE_RESAMPLER_LIMITS_H */
|
||||
232
Engine/lib/openal-soft/core/rtkit.cpp
Normal file
232
Engine/lib/openal-soft/core/rtkit.cpp
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*-*- Mode: C; c-basic-offset: 8 -*-*/
|
||||
|
||||
/***
|
||||
Copyright 2009 Lennart Poettering
|
||||
Copyright 2010 David Henningsson <diwic@ubuntu.com>
|
||||
Copyright 2021 Chris Robinson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
***/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "rtkit.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
|
||||
namespace dbus {
|
||||
|
||||
constexpr int TypeString{'s'};
|
||||
constexpr int TypeVariant{'v'};
|
||||
constexpr int TypeInt32{'i'};
|
||||
constexpr int TypeUInt32{'u'};
|
||||
constexpr int TypeInt64{'x'};
|
||||
constexpr int TypeUInt64{'t'};
|
||||
constexpr int TypeInvalid{'\0'};
|
||||
|
||||
struct MessageDeleter {
|
||||
void operator()(DBusMessage *m) { dbus_message_unref(m); }
|
||||
};
|
||||
using MessagePtr = std::unique_ptr<DBusMessage,MessageDeleter>;
|
||||
|
||||
} // namespace dbus
|
||||
|
||||
namespace {
|
||||
|
||||
inline pid_t _gettid()
|
||||
{
|
||||
#ifdef __linux__
|
||||
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||
#elif defined(__FreeBSD__)
|
||||
long pid{};
|
||||
thr_self(&pid);
|
||||
return static_cast<pid_t>(pid);
|
||||
#else
|
||||
#warning gettid not available
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int translate_error(const char *name)
|
||||
{
|
||||
if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0)
|
||||
return -ENOMEM;
|
||||
if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0
|
||||
|| strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0)
|
||||
return -ENOENT;
|
||||
if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0
|
||||
|| strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0)
|
||||
return -EACCES;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval)
|
||||
{
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.DBus.Properties", "Get")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
const char *interfacestr = RTKIT_SERVICE_NAME;
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeString, &interfacestr,
|
||||
dbus::TypeString, &propname,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
int ret{-EBADMSG};
|
||||
DBusMessageIter iter{};
|
||||
dbus_message_iter_init(r.get(), &iter);
|
||||
while(int curtype{dbus_message_iter_get_arg_type(&iter)})
|
||||
{
|
||||
if(curtype == dbus::TypeVariant)
|
||||
{
|
||||
DBusMessageIter subiter{};
|
||||
dbus_message_iter_recurse(&iter, &subiter);
|
||||
|
||||
while((curtype=dbus_message_iter_get_arg_type(&subiter)) != dbus::TypeInvalid)
|
||||
{
|
||||
if(curtype == dbus::TypeInt32)
|
||||
{
|
||||
dbus_int32_t i32{};
|
||||
dbus_message_iter_get_basic(&subiter, &i32);
|
||||
*propval = i32;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if(curtype == dbus::TypeInt64)
|
||||
{
|
||||
dbus_int64_t i64{};
|
||||
dbus_message_iter_get_basic(&subiter, &i64);
|
||||
*propval = i64;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&subiter);
|
||||
}
|
||||
}
|
||||
dbus_message_iter_next(&iter);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *connection)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)};
|
||||
return err < 0 ? err : static_cast<int>(retval);
|
||||
}
|
||||
|
||||
int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)};
|
||||
if(err >= 0) *min_nice_level = static_cast<int>(retval);
|
||||
return err;
|
||||
}
|
||||
|
||||
long long rtkit_get_rttime_usec_max(DBusConnection *connection)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)};
|
||||
return err < 0 ? err : retval;
|
||||
}
|
||||
|
||||
int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
if(thread == 0)
|
||||
return -ENOTSUP;
|
||||
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.RealtimeKit1", "MakeThreadRealtime")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
auto u64 = static_cast<dbus_uint64_t>(thread);
|
||||
auto u32 = static_cast<dbus_uint32_t>(priority);
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeUInt64, &u64,
|
||||
dbus::TypeUInt32, &u32,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
if(thread == 0)
|
||||
return -ENOTSUP;
|
||||
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
auto u64 = static_cast<dbus_uint64_t>(thread);
|
||||
auto s32 = static_cast<dbus_int32_t>(nice_level);
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeUInt64, &u64,
|
||||
dbus::TypeInt32, &s32,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
71
Engine/lib/openal-soft/core/rtkit.h
Normal file
71
Engine/lib/openal-soft/core/rtkit.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*-*- Mode: C; c-basic-offset: 8 -*-*/
|
||||
|
||||
#ifndef foortkithfoo
|
||||
#define foortkithfoo
|
||||
|
||||
/***
|
||||
Copyright 2009 Lennart Poettering
|
||||
Copyright 2010 David Henningsson <diwic@ubuntu.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
***/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
|
||||
/* This is the reference implementation for a client for
|
||||
* RealtimeKit. You don't have to use this, but if do, just copy these
|
||||
* sources into your repository */
|
||||
|
||||
#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
|
||||
#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
|
||||
|
||||
/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, {
|
||||
* .sched_priority = priority }). 'thread' needs to be a kernel thread
|
||||
* id as returned by gettid(), not a pthread_t! If 'thread' is 0 the
|
||||
* current thread is used. The returned value is a negative errno
|
||||
* style error code, or 0 on success. */
|
||||
int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority);
|
||||
|
||||
/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread,
|
||||
* nice_level). 'thread' needs to be a kernel thread id as returned by
|
||||
* gettid(), not a pthread_t! If 'thread' is 0 the current thread is
|
||||
* used. The returned value is a negative errno style error code, or 0
|
||||
* on success.*/
|
||||
int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level);
|
||||
|
||||
/* Return the maximum value of realtime priority available. Realtime requests
|
||||
* above this value will fail. A negative value is an errno style error code.
|
||||
*/
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *system_bus);
|
||||
|
||||
/* Retreive the minimum value of nice level available. High prio requests
|
||||
* below this value will fail. The returned value is a negative errno
|
||||
* style error code, or 0 on success.*/
|
||||
int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level);
|
||||
|
||||
/* Return the maximum value of RLIMIT_RTTIME to set before attempting a
|
||||
* realtime request. A negative value is an errno style error code.
|
||||
*/
|
||||
long long rtkit_get_rttime_usec_max(DBusConnection *system_bus);
|
||||
|
||||
#endif
|
||||
|
|
@ -3,273 +3,239 @@
|
|||
|
||||
#include "uhjfilter.h"
|
||||
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(HAVE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alnumeric.h"
|
||||
#include "opthelpers.h"
|
||||
#include "phase_shifter.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
struct PhaseShifterT {
|
||||
alignas(16) std::array<float,Uhj2Encoder::sFilterSize> Coeffs;
|
||||
|
||||
/* Some notes on this filter construction.
|
||||
*
|
||||
* A wide-band phase-shift filter needs a delay to maintain linearity. A
|
||||
* dirac impulse in the center of a time-domain buffer represents a filter
|
||||
* passing all frequencies through as-is with a pure delay. Converting that
|
||||
* to the frequency domain, adjusting the phase of each frequency bin by
|
||||
* +90 degrees, then converting back to the time domain, results in a FIR
|
||||
* filter that applies a +90 degree wide-band phase-shift.
|
||||
*
|
||||
* A particularly notable aspect of the time-domain filter response is that
|
||||
* every other coefficient is 0. This allows doubling the effective size of
|
||||
* the filter, by storing only the non-0 coefficients and double-stepping
|
||||
* over the input to apply it.
|
||||
*
|
||||
* Additionally, the resulting filter is independent of the sample rate.
|
||||
* The same filter can be applied regardless of the device's sample rate
|
||||
* and achieve the same effect.
|
||||
*/
|
||||
PhaseShifterT()
|
||||
{
|
||||
constexpr size_t fft_size{Uhj2Encoder::sFilterSize * 2};
|
||||
constexpr size_t half_size{fft_size / 2};
|
||||
|
||||
/* Generate a frequency domain impulse with a +90 degree phase offset.
|
||||
* Reconstruct the mirrored frequencies to convert to the time domain.
|
||||
*/
|
||||
auto fftBuffer = std::make_unique<complex_d[]>(fft_size);
|
||||
std::fill_n(fftBuffer.get(), fft_size, complex_d{});
|
||||
fftBuffer[half_size] = 1.0;
|
||||
|
||||
forward_fft({fftBuffer.get(), fft_size});
|
||||
for(size_t i{0};i < half_size+1;++i)
|
||||
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
|
||||
for(size_t i{half_size+1};i < fft_size;++i)
|
||||
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
|
||||
inverse_fft({fftBuffer.get(), fft_size});
|
||||
|
||||
/* Reverse the filter for simpler processing, and store only the non-0
|
||||
* coefficients.
|
||||
*/
|
||||
auto fftiter = fftBuffer.get() + half_size + (Uhj2Encoder::sFilterSize-1);
|
||||
for(float &coeff : Coeffs)
|
||||
{
|
||||
coeff = static_cast<float>(fftiter->real() / double{fft_size});
|
||||
fftiter -= 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
const PhaseShifterT PShift{};
|
||||
|
||||
void allpass_process(al::span<float> dst, const float *RESTRICT src)
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
size_t pos{0};
|
||||
if(size_t todo{dst.size()>>1})
|
||||
{
|
||||
do {
|
||||
__m128 r04{_mm_setzero_ps()};
|
||||
__m128 r14{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < PShift.Coeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&PShift.Coeffs[j])};
|
||||
const __m128 s0{_mm_loadu_ps(&src[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])};
|
||||
|
||||
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
|
||||
r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
r04 = _mm_add_ps(r04, _mm_shuffle_ps(r04, r04, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r04 = _mm_add_ps(r04, _mm_movehl_ps(r04, r04));
|
||||
dst[pos++] += _mm_cvtss_f32(r04);
|
||||
|
||||
r14 = _mm_add_ps(r14, _mm_shuffle_ps(r14, r14, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r14 = _mm_add_ps(r14, _mm_movehl_ps(r14, r14));
|
||||
dst[pos++] += _mm_cvtss_f32(r14);
|
||||
|
||||
src += 2;
|
||||
} while(--todo);
|
||||
}
|
||||
if((dst.size()&1))
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < PShift.Coeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&PShift.Coeffs[j])};
|
||||
/* NOTE: This could alternatively be done with two unaligned loads
|
||||
* and a shuffle. Which would be better?
|
||||
*/
|
||||
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
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));
|
||||
|
||||
dst[pos] += _mm_cvtss_f32(r4);
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
size_t pos{0};
|
||||
if(size_t todo{dst.size()>>1})
|
||||
{
|
||||
/* There doesn't seem to be NEON intrinsics to do this kind of stipple
|
||||
* shuffling, so there's two custom methods for it.
|
||||
*/
|
||||
auto shuffle_2020 = [](float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3);
|
||||
return ret;
|
||||
};
|
||||
auto shuffle_3131 = [](float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3);
|
||||
return ret;
|
||||
};
|
||||
do {
|
||||
float32x4_t r04{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r14{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < PShift.Coeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&PShift.Coeffs[j])};
|
||||
const float32x4_t s0{vld1q_f32(&src[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
|
||||
|
||||
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
|
||||
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
|
||||
}
|
||||
r04 = vaddq_f32(r04, vrev64q_f32(r04));
|
||||
dst[pos++] = vget_lane_f32(vadd_f32(vget_low_f32(r04), vget_high_f32(r04)), 0);
|
||||
|
||||
r14 = vaddq_f32(r14, vrev64q_f32(r14));
|
||||
dst[pos++] = vget_lane_f32(vadd_f32(vget_low_f32(r14), vget_high_f32(r14)), 0);
|
||||
|
||||
src += 2;
|
||||
} while(--todo);
|
||||
}
|
||||
if((dst.size()&1))
|
||||
{
|
||||
auto load4 = [](float32_t a, float32_t b, float32_t c, float32_t d)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(a)};
|
||||
ret = vsetq_lane_f32(b, ret, 1);
|
||||
ret = vsetq_lane_f32(c, ret, 2);
|
||||
ret = vsetq_lane_f32(d, ret, 3);
|
||||
return ret;
|
||||
};
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < PShift.Coeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&PShift.Coeffs[j])};
|
||||
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < PShift.Coeffs.size();++j)
|
||||
ret += src[j*2] * PShift.Coeffs[j];
|
||||
|
||||
output += ret;
|
||||
++src;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
const PhaseShifterT<UhjFilterBase::sFilterDelay*2> PShift{};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
/* Encoding 2-channel UHJ from B-Format is done as:
|
||||
/* Encoding 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
|
||||
* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
|
||||
* Q = 0.9772*Z
|
||||
*
|
||||
* where j is a wide-band +90 degree phase shift.
|
||||
* where j is a wide-band +90 degree phase shift. 3-channel UHJ excludes Q,
|
||||
* while 2-channel excludes Q and T.
|
||||
*
|
||||
* The phase shift is done using a FIR filter derived from an FFT'd impulse
|
||||
* with the desired shift.
|
||||
* The phase shift is done using a linear FIR filter derived from an FFT'd
|
||||
* impulse with the desired shift.
|
||||
*/
|
||||
|
||||
void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo)
|
||||
void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())};
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut.data())};
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
|
||||
|
||||
/* Combine the previously delayed mid/side signal with the input. */
|
||||
/* Combine the previously delayed S/D signal with the input. Include any
|
||||
* existing direct signal with it.
|
||||
*/
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
auto miditer = std::copy(mMidDelay.cbegin(), mMidDelay.cend(), mMid.begin());
|
||||
auto miditer = mS.begin() + sFilterDelay;
|
||||
std::transform(winput, winput+SamplesToDo, xinput, miditer,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return 0.9396926f*w + 0.1855740f*x; });
|
||||
|
||||
/* D = 0.6554516*Y */
|
||||
auto sideiter = std::copy(mSideDelay.cbegin(), mSideDelay.cend(), mSide.begin());
|
||||
std::transform(yinput, yinput+SamplesToDo, sideiter,
|
||||
[](const float y) noexcept -> float { return 0.6554516f*y; });
|
||||
|
||||
/* Include any existing direct signal in the mid/side buffers. */
|
||||
for(size_t i{0};i < SamplesToDo;++i,++miditer)
|
||||
*miditer += left[i] + right[i];
|
||||
|
||||
/* D = 0.6554516*Y */
|
||||
auto sideiter = mD.begin() + sFilterDelay;
|
||||
std::transform(yinput, yinput+SamplesToDo, sideiter,
|
||||
[](const float y) noexcept -> float { return 0.6554516f*y; });
|
||||
for(size_t i{0};i < SamplesToDo;++i,++sideiter)
|
||||
*sideiter += left[i] - right[i];
|
||||
|
||||
/* Copy the future samples back to the delay buffers for next time. */
|
||||
std::copy_n(mMid.cbegin()+SamplesToDo, mMidDelay.size(), mMidDelay.begin());
|
||||
std::copy_n(mSide.cbegin()+SamplesToDo, mSideDelay.size(), mSideDelay.begin());
|
||||
|
||||
/* Now add the all-passed signal into the side signal. */
|
||||
|
||||
/* D += j(-0.3420201*W + 0.5098604*X) */
|
||||
auto tmpiter = std::copy(mSideHistory.cbegin(), mSideHistory.cend(), mTemp.begin());
|
||||
auto tmpiter = std::copy(mWXHistory.cbegin(), mWXHistory.cend(), mTemp.begin());
|
||||
std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return -0.3420201f*w + 0.5098604f*x; });
|
||||
std::copy_n(mTemp.cbegin()+SamplesToDo, mSideHistory.size(), mSideHistory.begin());
|
||||
allpass_process({mSide.data(), SamplesToDo}, mTemp.data());
|
||||
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin());
|
||||
PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data());
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
left[i] = (mMid[i] + mSide[i]) * 0.5f;
|
||||
left[i] = (mS[i] + mD[i]) * 0.5f;
|
||||
/* Right = (S - D)/2.0 */
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] = (mMid[i] - mSide[i]) * 0.5f;
|
||||
right[i] = (mS[i] - mD[i]) * 0.5f;
|
||||
|
||||
/* Copy the future samples to the front for next time. */
|
||||
std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin());
|
||||
std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin());
|
||||
}
|
||||
|
||||
|
||||
/* Decoding UHJ is done as:
|
||||
*
|
||||
* S = Left + Right
|
||||
* D = Left - Right
|
||||
*
|
||||
* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T)
|
||||
* X = 0.418496*S - j(0.828331*D + 0.767820*T)
|
||||
* Y = 0.795968*D - 0.676392*T + j(0.186633*S)
|
||||
* Z = 1.023332*Q
|
||||
*
|
||||
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
|
||||
* channel excludes Q and T.
|
||||
*/
|
||||
void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples)
|
||||
{
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
const float *RESTRICT t{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
|
||||
/* T */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
mT[i] = t[i];
|
||||
}
|
||||
|
||||
float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
|
||||
float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
|
||||
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sFilterDelay, mT.cbegin(), tmpiter,
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin());
|
||||
PShift.process({xoutput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i];
|
||||
/* X = 0.418496*S - j(0.828331*D + 0.767820*T) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
xoutput[i] = 0.418496f*mS[i] - xoutput[i];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin());
|
||||
PShift.process({youtput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i];
|
||||
|
||||
if(samples.size() > 3)
|
||||
{
|
||||
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
|
||||
/* Z = 1.023332*Q */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
zoutput[i] = 1.023332f*zoutput[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Super Stereo processing is done as:
|
||||
*
|
||||
* S = Left + Right
|
||||
* D = Left - Right
|
||||
*
|
||||
* W = 0.6098637*S - 0.6896511*j*w*D
|
||||
* X = 0.8624776*S + 0.7626955*j*w*D
|
||||
* Y = 1.6822415*w*D - 0.2156194*j*S
|
||||
*
|
||||
* where j is a +90 degree phase shift. w is a variable control for the
|
||||
* resulting stereo width, with the range 0 <= w <= 0.7.
|
||||
*/
|
||||
void UhjDecoder::decodeStereo(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples)
|
||||
{
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* Pre-apply the width factor to the difference signal D. Smoothly
|
||||
* interpolate when it changes.
|
||||
*/
|
||||
const float wtarget{mWidthControl};
|
||||
const float wcurrent{unlikely(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(likely(wtarget == wcurrent) || unlikely(forwardSamples == 0))
|
||||
{
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
mD[i] = (left[i] - right[i]) * wcurrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(forwardSamples)};
|
||||
float fi{0.0f};
|
||||
size_t i{0};
|
||||
for(;i < forwardSamples;++i)
|
||||
{
|
||||
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
|
||||
fi += 1.0f;
|
||||
}
|
||||
for(;i < samplesToDo+sFilterDelay;++i)
|
||||
mD[i] = (left[i] - right[i]) * wtarget;
|
||||
mCurrentWidth = wtarget;
|
||||
}
|
||||
}
|
||||
|
||||
float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
|
||||
float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
|
||||
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* Precompute j*D and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mD.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin());
|
||||
PShift.process({xoutput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* W = 0.6098637*S - 0.6896511*j*w*D */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i];
|
||||
/* X = 0.8624776*S + 0.7626955*j*w*D */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin());
|
||||
PShift.process({youtput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* Y = 1.6822415*w*D - 0.2156194*j*S */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,35 +5,80 @@
|
|||
|
||||
#include "almalloc.h"
|
||||
#include "bufferline.h"
|
||||
#include "resampler_limits.h"
|
||||
|
||||
|
||||
struct Uhj2Encoder {
|
||||
/* A particular property of the filter allows it to cover nearly twice its
|
||||
* length, so the filter size is also the effective delay (despite being
|
||||
* center-aligned).
|
||||
struct UhjFilterBase {
|
||||
/* The filter delay is half it's effective size, so a delay of 128 has a
|
||||
* FIR length of 256.
|
||||
*/
|
||||
constexpr static size_t sFilterSize{128};
|
||||
static constexpr size_t sFilterDelay{128};
|
||||
};
|
||||
|
||||
/* Delays for the unfiltered signal. */
|
||||
alignas(16) std::array<float,sFilterSize> mMidDelay{};
|
||||
alignas(16) std::array<float,sFilterSize> mSideDelay{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterSize> mMid{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterSize> mSide{};
|
||||
struct UhjEncoder : public UhjFilterBase {
|
||||
/* Delays and processing storage for the unfiltered signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
|
||||
|
||||
/* History for the FIR filter. */
|
||||
alignas(16) std::array<float,sFilterSize*2 - 1> mSideHistory{};
|
||||
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sFilterSize*2> mTemp{};
|
||||
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and scaling.
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo);
|
||||
void encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
DEF_NEWDEL(Uhj2Encoder)
|
||||
DEF_NEWDEL(UhjEncoder)
|
||||
};
|
||||
|
||||
|
||||
struct UhjDecoder : public UhjFilterBase {
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mT{};
|
||||
|
||||
alignas(16) std::array<float,sFilterDelay-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sFilterDelay-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge + sFilterDelay*2> mTemp{};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
/**
|
||||
* The width factor for Super Stereo processing. Can be changed in between
|
||||
* calls to decodeStereo, with valid values being between 0...0.7.
|
||||
*/
|
||||
float mWidthControl{0.593f};
|
||||
|
||||
/**
|
||||
* Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa
|
||||
* channel ordering and UHJ scaling. For 3-channel, the 3rd channel may be
|
||||
* attenuated by 'n', where 0 <= n <= 1. So to decode 2-channel UHJ, supply
|
||||
* 3 channels with the 3rd channel silent (n=0). The B-Format signal
|
||||
* reconstructed from 2-channel UHJ should not be run through a normal
|
||||
* B-Format decoder, as it needs different shelf filters.
|
||||
*/
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples);
|
||||
|
||||
/**
|
||||
* Applies Super Stereo processing on a stereo signal to create a B-Format
|
||||
* signal with FuMa channel ordering and UHJ scaling. The samples span
|
||||
* should contain 3 channels, the first two being the left and right stereo
|
||||
* channels, and the third left empty.
|
||||
*/
|
||||
void decodeStereo(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples);
|
||||
|
||||
using DecoderFunc = void (UhjDecoder::*)(const al::span<float*> samples,
|
||||
const size_t samplesToDo, const size_t forwardSamples);
|
||||
|
||||
DEF_NEWDEL(UhjDecoder)
|
||||
};
|
||||
|
||||
#endif /* CORE_UHJFILTER_H */
|
||||
|
|
|
|||
37
Engine/lib/openal-soft/core/uiddefs.cpp
Normal file
37
Engine/lib/openal-soft/core/uiddefs.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
#ifndef AL_NO_UID_DEFS
|
||||
|
||||
#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
|
||||
#define INITGUID
|
||||
#include <windows.h>
|
||||
#ifdef HAVE_GUIDDEF_H
|
||||
#include <guiddef.h>
|
||||
#else
|
||||
#include <initguid.h>
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
|
||||
|
||||
DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16);
|
||||
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
|
||||
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
|
||||
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
|
||||
DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
|
||||
DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
|
||||
|
||||
#ifdef HAVE_WASAPI
|
||||
#include <wtypes.h>
|
||||
#include <devpropdef.h>
|
||||
#include <propkeydef.h>
|
||||
DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
|
||||
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
|
||||
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* AL_NO_UID_DEFS */
|
||||
945
Engine/lib/openal-soft/core/voice.cpp
Normal file
945
Engine/lib/openal-soft/core/voice.cpp
Normal file
|
|
@ -0,0 +1,945 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "voice.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "alstring.h"
|
||||
#include "ambidefs.h"
|
||||
#include "async_event.h"
|
||||
#include "buffer_storage.h"
|
||||
#include "context.h"
|
||||
#include "cpu_caps.h"
|
||||
#include "devformat.h"
|
||||
#include "device.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "filters/nfc.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "fmt_traits.h"
|
||||
#include "logging.h"
|
||||
#include "mixer.h"
|
||||
#include "mixer/defs.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "opthelpers.h"
|
||||
#include "resampler_limits.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "vector.h"
|
||||
#include "voice_change.h"
|
||||
|
||||
struct CTag;
|
||||
#ifdef HAVE_SSE
|
||||
struct SSETag;
|
||||
#endif
|
||||
#ifdef HAVE_NEON
|
||||
struct NEONTag;
|
||||
#endif
|
||||
struct CopyTag;
|
||||
|
||||
|
||||
static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15),
|
||||
"DeviceBase::MixerBufferLine must be a multiple of 16 bytes");
|
||||
static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4");
|
||||
|
||||
Resampler ResamplerDefault{Resampler::Linear};
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize);
|
||||
using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples,
|
||||
const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
|
||||
const size_t BufferSize);
|
||||
|
||||
HrtfMixerFunc MixHrtfSamples{MixHrtf_<CTag>};
|
||||
HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_<CTag>};
|
||||
|
||||
inline MixerFunc SelectMixer()
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return Mix_<NEONTag>;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return Mix_<SSETag>;
|
||||
#endif
|
||||
return Mix_<CTag>;
|
||||
}
|
||||
|
||||
inline HrtfMixerFunc SelectHrtfMixer()
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return MixHrtf_<NEONTag>;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return MixHrtf_<SSETag>;
|
||||
#endif
|
||||
return MixHrtf_<CTag>;
|
||||
}
|
||||
|
||||
inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return MixHrtfBlend_<NEONTag>;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return MixHrtfBlend_<SSETag>;
|
||||
#endif
|
||||
return MixHrtfBlend_<CTag>;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Voice::InitMixer(al::optional<std::string> resampler)
|
||||
{
|
||||
if(resampler)
|
||||
{
|
||||
struct ResamplerEntry {
|
||||
const char name[16];
|
||||
const Resampler resampler;
|
||||
};
|
||||
constexpr ResamplerEntry ResamplerList[]{
|
||||
{ "none", Resampler::Point },
|
||||
{ "point", Resampler::Point },
|
||||
{ "linear", Resampler::Linear },
|
||||
{ "cubic", Resampler::Cubic },
|
||||
{ "bsinc12", Resampler::BSinc12 },
|
||||
{ "fast_bsinc12", Resampler::FastBSinc12 },
|
||||
{ "bsinc24", Resampler::BSinc24 },
|
||||
{ "fast_bsinc24", Resampler::FastBSinc24 },
|
||||
};
|
||||
|
||||
const char *str{resampler->c_str()};
|
||||
if(al::strcasecmp(str, "bsinc") == 0)
|
||||
{
|
||||
WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
|
||||
str = "bsinc12";
|
||||
}
|
||||
else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0)
|
||||
{
|
||||
WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
|
||||
str = "cubic";
|
||||
}
|
||||
|
||||
auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList),
|
||||
[str](const ResamplerEntry &entry) -> bool
|
||||
{ return al::strcasecmp(str, entry.name) == 0; });
|
||||
if(iter == std::end(ResamplerList))
|
||||
ERR("Invalid resampler: %s\n", str);
|
||||
else
|
||||
ResamplerDefault = iter->resampler;
|
||||
}
|
||||
|
||||
MixSamples = SelectMixer();
|
||||
MixHrtfBlendSamples = SelectHrtfBlendMixer();
|
||||
MixHrtfSamples = SelectHrtfMixer();
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
void SendSourceStoppedEvent(ContextBase *context, uint id)
|
||||
{
|
||||
RingBuffer *ring{context->mAsyncEvents.get()};
|
||||
auto evt_vec = ring->getWriteVector();
|
||||
if(evt_vec.first.len < 1) return;
|
||||
|
||||
AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf),
|
||||
AsyncEvent::SourceStateChange)};
|
||||
evt->u.srcstate.id = id;
|
||||
evt->u.srcstate.state = AsyncEvent::SrcState::Stop;
|
||||
|
||||
ring->writeAdvance(1);
|
||||
}
|
||||
|
||||
|
||||
const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst,
|
||||
const al::span<const float> src, int type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case AF_None:
|
||||
lpfilter.clear();
|
||||
hpfilter.clear();
|
||||
break;
|
||||
|
||||
case AF_LowPass:
|
||||
lpfilter.process(src, dst);
|
||||
hpfilter.clear();
|
||||
return dst;
|
||||
case AF_HighPass:
|
||||
lpfilter.clear();
|
||||
hpfilter.process(src, dst);
|
||||
return dst;
|
||||
|
||||
case AF_BandPass:
|
||||
DualBiquad{lpfilter, hpfilter}.process(src, dst);
|
||||
return dst;
|
||||
}
|
||||
return src.data();
|
||||
}
|
||||
|
||||
|
||||
template<FmtType Type>
|
||||
inline void LoadSamples(const al::span<float*> dstSamples, const size_t dstOffset,
|
||||
const al::byte *src, const size_t srcOffset, const FmtChannels srcChans, const size_t srcStep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits<Type>::Type)};
|
||||
auto s = src + srcOffset*srcStep*sampleSize;
|
||||
if(srcChans == FmtUHJ2 || srcChans == FmtSuperStereo)
|
||||
{
|
||||
al::LoadSampleArray<Type>(dstSamples[0]+dstOffset, s, srcStep, samples);
|
||||
al::LoadSampleArray<Type>(dstSamples[1]+dstOffset, s+sampleSize, srcStep, samples);
|
||||
std::fill_n(dstSamples[2]+dstOffset, samples, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto *dst : dstSamples)
|
||||
{
|
||||
al::LoadSampleArray<Type>(dst+dstOffset, s, srcStep, samples);
|
||||
s += sampleSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSamples(const al::span<float*> dstSamples, const size_t dstOffset, const al::byte *src,
|
||||
const size_t srcOffset, const FmtType srcType, const FmtChannels srcChans,
|
||||
const size_t srcStep, const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: \
|
||||
LoadSamples<T>(dstSamples, dstOffset, src, srcOffset, srcChans, 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
|
||||
}
|
||||
|
||||
void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
|
||||
const size_t srcStep, const size_t samplesToLoad, const al::span<float*> voiceSamples)
|
||||
{
|
||||
const uint loopStart{buffer->mLoopStart};
|
||||
const uint loopEnd{buffer->mLoopEnd};
|
||||
ASSUME(loopEnd > loopStart);
|
||||
|
||||
/* If current pos is beyond the loop range, do not loop */
|
||||
if(!bufferLoopItem || dataPosInt >= loopEnd)
|
||||
{
|
||||
/* Load what's left to play from the buffer */
|
||||
const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)};
|
||||
LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels,
|
||||
srcStep, remaining);
|
||||
|
||||
if(const size_t toFill{samplesToLoad - remaining})
|
||||
{
|
||||
for(auto *chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer + remaining - 1;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Load what's left of this loop iteration */
|
||||
const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)};
|
||||
LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels,
|
||||
srcStep, remaining);
|
||||
|
||||
/* Load repeats of the loop to fill the buffer. */
|
||||
const auto loopSize = static_cast<size_t>(loopEnd - loopStart);
|
||||
size_t samplesLoaded{remaining};
|
||||
while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)})
|
||||
{
|
||||
LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType,
|
||||
sampleChannels, srcStep, toFill);
|
||||
samplesLoaded += toFill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples,
|
||||
const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep,
|
||||
const size_t samplesToLoad, const al::span<float*> voiceSamples)
|
||||
{
|
||||
/* Load what's left to play from the buffer */
|
||||
const size_t remaining{minz(samplesToLoad, numCallbackSamples)};
|
||||
LoadSamples(voiceSamples, 0, buffer->mSamples, 0, sampleType, sampleChannels, srcStep,
|
||||
remaining);
|
||||
|
||||
if(const size_t toFill{samplesToLoad - remaining})
|
||||
{
|
||||
for(auto *chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer + remaining - 1;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
|
||||
const size_t srcStep, const size_t samplesToLoad, const al::span<float*> voiceSamples)
|
||||
{
|
||||
/* Crawl the buffer queue to fill in the temp buffer */
|
||||
size_t samplesLoaded{0};
|
||||
while(buffer && samplesLoaded != samplesToLoad)
|
||||
{
|
||||
if(dataPosInt >= buffer->mSampleLen)
|
||||
{
|
||||
dataPosInt -= buffer->mSampleLen;
|
||||
buffer = buffer->mNext.load(std::memory_order_acquire);
|
||||
if(!buffer) buffer = bufferLoopItem;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)};
|
||||
LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType,
|
||||
sampleChannels, srcStep, remaining);
|
||||
|
||||
samplesLoaded += remaining;
|
||||
if(samplesLoaded == samplesToLoad)
|
||||
break;
|
||||
|
||||
dataPosInt = 0;
|
||||
buffer = buffer->mNext.load(std::memory_order_acquire);
|
||||
if(!buffer) buffer = bufferLoopItem;
|
||||
}
|
||||
if(const size_t toFill{samplesToLoad - samplesLoaded})
|
||||
{
|
||||
size_t chanidx{0};
|
||||
for(auto *chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer + samplesLoaded - 1;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
++chanidx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms,
|
||||
const float TargetGain, const uint Counter, uint OutPos, const bool IsPlaying,
|
||||
DeviceBase *Device)
|
||||
{
|
||||
const uint IrSize{Device->mIrSize};
|
||||
auto &HrtfSamples = Device->HrtfSourceData;
|
||||
auto &AccumSamples = Device->HrtfAccumData;
|
||||
|
||||
/* Copy the HRTF history and new input samples into a temp buffer. */
|
||||
auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(),
|
||||
std::begin(HrtfSamples));
|
||||
std::copy_n(samples, DstBufferSize, src_iter);
|
||||
/* Copy the last used samples back into the history buffer for later. */
|
||||
if(likely(IsPlaying))
|
||||
std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(),
|
||||
parms.Hrtf.History.begin());
|
||||
|
||||
/* If fading and this is the first mixing pass, fade between the IRs. */
|
||||
uint fademix{0u};
|
||||
if(Counter && OutPos == 0)
|
||||
{
|
||||
fademix = minu(DstBufferSize, Counter);
|
||||
|
||||
float gain{TargetGain};
|
||||
|
||||
/* The new coefficients need to fade in completely since they're
|
||||
* replacing the old ones. To keep the gain fading consistent,
|
||||
* interpolate between the old and new target gains given how much of
|
||||
* the fade time this mix handles.
|
||||
*/
|
||||
if(Counter > fademix)
|
||||
{
|
||||
const float a{static_cast<float>(fademix) / static_cast<float>(Counter)};
|
||||
gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a);
|
||||
}
|
||||
|
||||
MixHrtfFilter hrtfparams{
|
||||
parms.Hrtf.Target.Coeffs,
|
||||
parms.Hrtf.Target.Delay,
|
||||
0.0f, gain / static_cast<float>(fademix)};
|
||||
MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams,
|
||||
fademix);
|
||||
|
||||
/* Update the old parameters with the result. */
|
||||
parms.Hrtf.Old = parms.Hrtf.Target;
|
||||
parms.Hrtf.Old.Gain = gain;
|
||||
OutPos += fademix;
|
||||
}
|
||||
|
||||
if(fademix < DstBufferSize)
|
||||
{
|
||||
const uint todo{DstBufferSize - fademix};
|
||||
float gain{TargetGain};
|
||||
|
||||
/* Interpolate the target gain if the gain fading lasts longer than
|
||||
* this mix.
|
||||
*/
|
||||
if(Counter > DstBufferSize)
|
||||
{
|
||||
const float a{static_cast<float>(todo) / static_cast<float>(Counter-fademix)};
|
||||
gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a);
|
||||
}
|
||||
|
||||
MixHrtfFilter hrtfparams{
|
||||
parms.Hrtf.Target.Coeffs,
|
||||
parms.Hrtf.Target.Delay,
|
||||
parms.Hrtf.Old.Gain,
|
||||
(gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo)};
|
||||
MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo);
|
||||
|
||||
/* Store the now-current gain for next time. */
|
||||
parms.Hrtf.Old.Gain = gain;
|
||||
}
|
||||
}
|
||||
|
||||
void DoNfcMix(const al::span<const float> samples, FloatBufferLine *OutBuffer, DirectParams &parms,
|
||||
const float *TargetGains, const uint Counter, const uint OutPos, DeviceBase *Device)
|
||||
{
|
||||
using FilterProc = void (NfcFilter::*)(const al::span<const float>, float*);
|
||||
static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{
|
||||
nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3};
|
||||
|
||||
float *CurrentGains{parms.Gains.Current.data()};
|
||||
MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos);
|
||||
++OutBuffer;
|
||||
++CurrentGains;
|
||||
++TargetGains;
|
||||
|
||||
const al::span<float> nfcsamples{Device->NfcSampleData, samples.size()};
|
||||
size_t order{1};
|
||||
while(const size_t chancount{Device->NumChannelsPerOrder[order]})
|
||||
{
|
||||
(parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data());
|
||||
MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos);
|
||||
OutBuffer += chancount;
|
||||
CurrentGains += chancount;
|
||||
TargetGains += chancount;
|
||||
if(++order == MaxAmbiOrder+1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo)
|
||||
{
|
||||
static constexpr std::array<float,MAX_OUTPUT_CHANNELS> SilentTarget{};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* Get voice info */
|
||||
uint DataPosInt{mPosition.load(std::memory_order_relaxed)};
|
||||
uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)};
|
||||
VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)};
|
||||
VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)};
|
||||
const uint increment{mStep};
|
||||
if UNLIKELY(increment < 1)
|
||||
{
|
||||
/* If the voice is supposed to be stopping but can't be mixed, just
|
||||
* stop it before bailing.
|
||||
*/
|
||||
if(vstate == Stopping)
|
||||
mPlayState.store(Stopped, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceBase *Device{Context->mDevice};
|
||||
const uint NumSends{Device->NumAuxSends};
|
||||
|
||||
ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ?
|
||||
Resample_<CopyTag,CTag> : mResampler};
|
||||
|
||||
uint Counter{mFlags.test(VoiceIsFading) ? SamplesToDo : 0};
|
||||
if(!Counter)
|
||||
{
|
||||
/* No fading, just overwrite the old/current params. */
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
{
|
||||
DirectParams &parms = chandata.mDryParams;
|
||||
if(!mFlags.test(VoiceHasHrtf))
|
||||
parms.Gains.Current = parms.Gains.Target;
|
||||
else
|
||||
parms.Hrtf.Old = parms.Hrtf.Target;
|
||||
}
|
||||
for(uint send{0};send < NumSends;++send)
|
||||
{
|
||||
if(mSend[send].Buffer.empty())
|
||||
continue;
|
||||
|
||||
SendParams &parms = chandata.mWetParams[send];
|
||||
parms.Gains.Current = parms.Gains.Target;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if UNLIKELY(!BufferListItem)
|
||||
Counter = std::min(Counter, 64u);
|
||||
|
||||
std::array<float*,DeviceBase::MixerChannelsMax> SamplePointers;
|
||||
const al::span<float*> MixingSamples{SamplePointers.data(), mChans.size()};
|
||||
auto offset_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float*
|
||||
{ return bufline.data() + MaxResamplerEdge; };
|
||||
std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(),
|
||||
MixingSamples.begin(), offset_bufferline);
|
||||
|
||||
const uint PostPadding{MaxResamplerEdge +
|
||||
(mDecoder ? uint{UhjDecoder::sFilterDelay} : 0u)};
|
||||
uint buffers_done{0u};
|
||||
uint OutPos{0u};
|
||||
do {
|
||||
/* Figure out how many buffer samples will be needed */
|
||||
uint DstBufferSize{SamplesToDo - OutPos};
|
||||
uint SrcBufferSize;
|
||||
|
||||
if(increment <= MixerFracOne)
|
||||
{
|
||||
/* Calculate the last written dst sample pos. */
|
||||
uint64_t DataSize64{DstBufferSize - 1};
|
||||
/* Calculate the last read src sample pos. */
|
||||
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
|
||||
/* +1 to get the src sample count, include padding. */
|
||||
DataSize64 += 1 + PostPadding;
|
||||
|
||||
/* Result is guaranteed to be <= BufferLineSize+PostPadding since
|
||||
* we won't use more src samples than dst samples+padding.
|
||||
*/
|
||||
SrcBufferSize = static_cast<uint>(DataSize64);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t DataSize64{DstBufferSize};
|
||||
/* Calculate the end src sample pos, include padding. */
|
||||
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
|
||||
DataSize64 += PostPadding;
|
||||
|
||||
if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge)
|
||||
SrcBufferSize = static_cast<uint>(DataSize64);
|
||||
else
|
||||
{
|
||||
/* If the source size got saturated, we can't fill the desired
|
||||
* dst size. Figure out how many samples we can actually mix.
|
||||
*/
|
||||
SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge;
|
||||
|
||||
DataSize64 = SrcBufferSize - PostPadding;
|
||||
DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
|
||||
if(DataSize64 < DstBufferSize)
|
||||
{
|
||||
/* Some mixers require being 16-byte aligned, so also limit
|
||||
* to a multiple of 4 samples to maintain alignment.
|
||||
*/
|
||||
DstBufferSize = static_cast<uint>(DataSize64) & ~3u;
|
||||
/* If the voice is stopping, only one mixing iteration will
|
||||
* be done, so ensure it fades out completely this mix.
|
||||
*/
|
||||
if(unlikely(vstate == Stopping))
|
||||
Counter = std::min(Counter, DstBufferSize);
|
||||
}
|
||||
ASSUME(DstBufferSize > 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(unlikely(!BufferListItem))
|
||||
{
|
||||
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
|
||||
auto prevSamples = mPrevSamples.data();
|
||||
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
|
||||
for(auto *chanbuffer : MixingSamples)
|
||||
{
|
||||
auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding,
|
||||
chanbuffer-MaxResamplerEdge);
|
||||
|
||||
/* When loading from a voice that ended prematurely, only take
|
||||
* the samples that get closest to 0 amplitude. This helps
|
||||
* certain sounds fade out better.
|
||||
*/
|
||||
auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
|
||||
{ return std::abs(lhs) < std::abs(rhs); };
|
||||
auto srciter = std::min_element(chanbuffer, srcend, abs_lt);
|
||||
|
||||
std::fill(srciter+1, chanbuffer + SrcBufferSize, *srciter);
|
||||
|
||||
std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(),
|
||||
prevSamples->data());
|
||||
++prevSamples;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prevSamples = mPrevSamples.data();
|
||||
for(auto *chanbuffer : MixingSamples)
|
||||
{
|
||||
std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge);
|
||||
++prevSamples;
|
||||
}
|
||||
if(mFlags.test(VoiceIsStatic))
|
||||
LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType,
|
||||
mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples);
|
||||
else if(mFlags.test(VoiceIsCallback))
|
||||
{
|
||||
if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples)
|
||||
{
|
||||
const size_t byteOffset{mNumCallbackSamples*mFrameSize};
|
||||
const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset};
|
||||
|
||||
const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
|
||||
&BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
|
||||
if(gotBytes < 0)
|
||||
mFlags.set(VoiceCallbackStopped);
|
||||
else if(static_cast<uint>(gotBytes) < needBytes)
|
||||
{
|
||||
mFlags.set(VoiceCallbackStopped);
|
||||
mNumCallbackSamples += static_cast<uint>(gotBytes) / mFrameSize;
|
||||
}
|
||||
else
|
||||
mNumCallbackSamples = SrcBufferSize;
|
||||
}
|
||||
LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels,
|
||||
mFrameStep, SrcBufferSize, MixingSamples);
|
||||
}
|
||||
else
|
||||
LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels,
|
||||
mFrameStep, SrcBufferSize, MixingSamples);
|
||||
|
||||
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
|
||||
if(mDecoder)
|
||||
{
|
||||
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
|
||||
((*mDecoder).*mDecoderFunc)(MixingSamples, SrcBufferSize,
|
||||
srcOffset * likely(vstate == Playing));
|
||||
}
|
||||
/* Store the last source samples used for next time. */
|
||||
if(likely(vstate == Playing))
|
||||
{
|
||||
prevSamples = mPrevSamples.data();
|
||||
for(auto *chanbuffer : MixingSamples)
|
||||
{
|
||||
/* Store the last source samples used for next time. */
|
||||
std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(),
|
||||
prevSamples->data());
|
||||
++prevSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto voiceSamples = MixingSamples.begin();
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
/* Resample, then apply ambisonic upsampling as needed. */
|
||||
float *ResampledData{Resample(&mResampleState, *voiceSamples, DataPosFrac, increment,
|
||||
{Device->ResampledData, DstBufferSize})};
|
||||
++voiceSamples;
|
||||
|
||||
if(mFlags.test(VoiceIsAmbisonic))
|
||||
chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize},
|
||||
chandata.mAmbiHFScale, chandata.mAmbiLFScale);
|
||||
|
||||
/* Now filter and mix to the appropriate outputs. */
|
||||
const al::span<float,BufferLineSize> FilterBuf{Device->FilteredData};
|
||||
{
|
||||
DirectParams &parms = chandata.mDryParams;
|
||||
const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
|
||||
{ResampledData, DstBufferSize}, mDirect.FilterType)};
|
||||
|
||||
if(mFlags.test(VoiceHasHrtf))
|
||||
{
|
||||
const float TargetGain{parms.Hrtf.Target.Gain * likely(vstate == Playing)};
|
||||
DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos,
|
||||
(vstate == Playing), Device);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data()
|
||||
: SilentTarget.data()};
|
||||
if(mFlags.test(VoiceHasNfc))
|
||||
DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms,
|
||||
TargetGains, Counter, OutPos, Device);
|
||||
else
|
||||
MixSamples({samples, DstBufferSize}, mDirect.Buffer,
|
||||
parms.Gains.Current.data(), TargetGains, Counter, OutPos);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint send{0};send < NumSends;++send)
|
||||
{
|
||||
if(mSend[send].Buffer.empty())
|
||||
continue;
|
||||
|
||||
SendParams &parms = chandata.mWetParams[send];
|
||||
const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
|
||||
{ResampledData, DstBufferSize}, mSend[send].FilterType)};
|
||||
|
||||
const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data()
|
||||
: SilentTarget.data()};
|
||||
MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
|
||||
parms.Gains.Current.data(), TargetGains, Counter, OutPos);
|
||||
}
|
||||
}
|
||||
/* If the voice is stopping, we're now done. */
|
||||
if(unlikely(vstate == Stopping))
|
||||
break;
|
||||
|
||||
/* Update positions */
|
||||
DataPosFrac += increment*DstBufferSize;
|
||||
const uint SrcSamplesDone{DataPosFrac>>MixerFracBits};
|
||||
DataPosInt += SrcSamplesDone;
|
||||
DataPosFrac &= MixerFracMask;
|
||||
|
||||
OutPos += DstBufferSize;
|
||||
Counter = maxu(DstBufferSize, Counter) - DstBufferSize;
|
||||
|
||||
if(unlikely(!BufferListItem))
|
||||
{
|
||||
/* Do nothing extra when there's no buffers. */
|
||||
}
|
||||
else if(mFlags.test(VoiceIsStatic))
|
||||
{
|
||||
if(BufferLoopItem)
|
||||
{
|
||||
/* Handle looping static source */
|
||||
const uint LoopStart{BufferListItem->mLoopStart};
|
||||
const uint LoopEnd{BufferListItem->mLoopEnd};
|
||||
if(DataPosInt >= LoopEnd)
|
||||
{
|
||||
assert(LoopEnd > LoopStart);
|
||||
DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Handle non-looping static source */
|
||||
if(DataPosInt >= BufferListItem->mSampleLen)
|
||||
{
|
||||
BufferListItem = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(mFlags.test(VoiceIsCallback))
|
||||
{
|
||||
/* Handle callback buffer source */
|
||||
if(SrcSamplesDone < mNumCallbackSamples)
|
||||
{
|
||||
const size_t byteOffset{SrcSamplesDone*mFrameSize};
|
||||
const size_t byteEnd{mNumCallbackSamples*mFrameSize};
|
||||
al::byte *data{BufferListItem->mSamples};
|
||||
std::copy(data+byteOffset, data+byteEnd, data);
|
||||
mNumCallbackSamples -= SrcSamplesDone;
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferListItem = nullptr;
|
||||
mNumCallbackSamples = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Handle streaming source */
|
||||
do {
|
||||
if(BufferListItem->mSampleLen > DataPosInt)
|
||||
break;
|
||||
|
||||
DataPosInt -= BufferListItem->mSampleLen;
|
||||
|
||||
++buffers_done;
|
||||
BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed);
|
||||
if(!BufferListItem) BufferListItem = BufferLoopItem;
|
||||
} while(BufferListItem);
|
||||
}
|
||||
} while(OutPos < SamplesToDo);
|
||||
|
||||
mFlags.set(VoiceIsFading);
|
||||
|
||||
/* Don't update positions and buffers if we were stopping. */
|
||||
if(unlikely(vstate == Stopping))
|
||||
{
|
||||
mPlayState.store(Stopped, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Capture the source ID in case it's reset for stopping. */
|
||||
const uint SourceID{mSourceID.load(std::memory_order_relaxed)};
|
||||
|
||||
/* Update voice info */
|
||||
mPosition.store(DataPosInt, std::memory_order_relaxed);
|
||||
mPositionFrac.store(DataPosFrac, std::memory_order_relaxed);
|
||||
mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed);
|
||||
if(!BufferListItem)
|
||||
{
|
||||
mLoopBuffer.store(nullptr, std::memory_order_relaxed);
|
||||
mSourceID.store(0u, std::memory_order_relaxed);
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
/* Send any events now, after the position/buffer info was updated. */
|
||||
const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)};
|
||||
if(buffers_done > 0 && (enabledevt&AsyncEvent::BufferCompleted))
|
||||
{
|
||||
RingBuffer *ring{Context->mAsyncEvents.get()};
|
||||
auto evt_vec = ring->getWriteVector();
|
||||
if(evt_vec.first.len > 0)
|
||||
{
|
||||
AsyncEvent *evt{al::construct_at(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf),
|
||||
AsyncEvent::BufferCompleted)};
|
||||
evt->u.bufcomp.id = SourceID;
|
||||
evt->u.bufcomp.count = buffers_done;
|
||||
ring->writeAdvance(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!BufferListItem)
|
||||
{
|
||||
/* If the voice just ended, set it to Stopping so the next render
|
||||
* ensures any residual noise fades to 0 amplitude.
|
||||
*/
|
||||
mPlayState.store(Stopping, std::memory_order_release);
|
||||
if((enabledevt&AsyncEvent::SourceStateChange))
|
||||
SendSourceStoppedEvent(Context, SourceID);
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::prepare(DeviceBase *device)
|
||||
{
|
||||
/* Even if storing really high order ambisonics, we only mix channels for
|
||||
* orders up to the device order. The rest are simply dropped.
|
||||
*/
|
||||
uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 :
|
||||
ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))};
|
||||
if(unlikely(num_channels > device->mSampleData.size()))
|
||||
{
|
||||
ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels,
|
||||
device->mSampleData.size(), mFmtChannels, mAmbiOrder);
|
||||
num_channels = static_cast<uint>(device->mSampleData.size());
|
||||
}
|
||||
if(mChans.capacity() > 2 && num_channels < mChans.capacity())
|
||||
{
|
||||
decltype(mChans){}.swap(mChans);
|
||||
decltype(mPrevSamples){}.swap(mPrevSamples);
|
||||
}
|
||||
mChans.reserve(maxu(2, num_channels));
|
||||
mChans.resize(num_channels);
|
||||
mPrevSamples.reserve(maxu(2, num_channels));
|
||||
mPrevSamples.resize(num_channels);
|
||||
|
||||
if(IsUHJ(mFmtChannels))
|
||||
{
|
||||
mDecoder = std::make_unique<UhjDecoder>();
|
||||
mDecoderFunc = (mFmtChannels == FmtSuperStereo) ? &UhjDecoder::decodeStereo
|
||||
: &UhjDecoder::decode;
|
||||
}
|
||||
else
|
||||
{
|
||||
mDecoder = nullptr;
|
||||
mDecoderFunc = nullptr;
|
||||
}
|
||||
|
||||
/* Clear the stepping value explicitly so the mixer knows not to mix this
|
||||
* until the update gets applied.
|
||||
*/
|
||||
mStep = 0;
|
||||
|
||||
/* Make sure the sample history is cleared. */
|
||||
std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{});
|
||||
|
||||
/* Don't need to set the VoiceIsAmbisonic flag if the device is not higher
|
||||
* order than the voice. No HF scaling is necessary to mix it.
|
||||
*/
|
||||
if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder)
|
||||
{
|
||||
const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ?
|
||||
AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()};
|
||||
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
|
||||
|
||||
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
chandata.mAmbiHFScale = scales[*(OrderFromChan++)];
|
||||
chandata.mAmbiLFScale = 1.0f;
|
||||
chandata.mAmbiSplitter = splitter;
|
||||
chandata.mDryParams = DirectParams{};
|
||||
chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter;
|
||||
std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{});
|
||||
}
|
||||
/* 2-channel UHJ needs different shelf filters. However, we can't just
|
||||
* use different shelf filters after mixing it and with any old speaker
|
||||
* setup the user has. To make this work, we apply the expected shelf
|
||||
* filters for decoding UHJ2 to quad (only needs LF scaling), and act
|
||||
* as if those 4 quad channels are encoded right back onto first-order
|
||||
* B-Format, which then upsamples to higher order as normal (only needs
|
||||
* HF scaling).
|
||||
*
|
||||
* This isn't perfect, but without an entirely separate and limited
|
||||
* UHJ2 path, it's better than nothing.
|
||||
*/
|
||||
if(mFmtChannels == FmtUHJ2)
|
||||
{
|
||||
mChans[0].mAmbiLFScale = 0.661f;
|
||||
mChans[1].mAmbiLFScale = 1.293f;
|
||||
mChans[2].mAmbiLFScale = 1.293f;
|
||||
}
|
||||
mFlags.set(VoiceIsAmbisonic);
|
||||
}
|
||||
else if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder)
|
||||
{
|
||||
/* 2-channel UHJ with first-order output also needs the shelf filter
|
||||
* correction applied, except with UHJ output (UHJ2->B-Format->UHJ2 is
|
||||
* identity, so don't mess with it).
|
||||
*/
|
||||
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
chandata.mAmbiHFScale = 1.0f;
|
||||
chandata.mAmbiLFScale = 1.0f;
|
||||
chandata.mAmbiSplitter = splitter;
|
||||
chandata.mDryParams = DirectParams{};
|
||||
chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter;
|
||||
std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{});
|
||||
}
|
||||
mChans[0].mAmbiLFScale = 0.661f;
|
||||
mChans[1].mAmbiLFScale = 1.293f;
|
||||
mChans[2].mAmbiLFScale = 1.293f;
|
||||
mFlags.set(VoiceIsAmbisonic);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
chandata.mDryParams = DirectParams{};
|
||||
chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter;
|
||||
std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{});
|
||||
}
|
||||
mFlags.reset(VoiceIsAmbisonic);
|
||||
}
|
||||
}
|
||||
276
Engine/lib/openal-soft/core/voice.h
Normal file
276
Engine/lib/openal-soft/core/voice.h
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#ifndef CORE_VOICE_H
|
||||
#define CORE_VOICE_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
#include "buffer_storage.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/biquad.h"
|
||||
#include "filters/nfc.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "mixer/defs.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "resampler_limits.h"
|
||||
#include "uhjfilter.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ContextBase;
|
||||
struct DeviceBase;
|
||||
struct EffectSlot;
|
||||
enum class DistanceModel : unsigned char;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
#define MAX_SENDS 6
|
||||
|
||||
|
||||
enum class SpatializeMode : unsigned char {
|
||||
Off,
|
||||
On,
|
||||
Auto
|
||||
};
|
||||
|
||||
enum class DirectMode : unsigned char {
|
||||
Off,
|
||||
DropMismatch,
|
||||
RemixMismatch
|
||||
};
|
||||
|
||||
|
||||
/* Maximum number of extra source samples that may need to be loaded, for
|
||||
* resampling or conversion purposes.
|
||||
*/
|
||||
constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay};
|
||||
|
||||
|
||||
enum {
|
||||
AF_None = 0,
|
||||
AF_LowPass = 1,
|
||||
AF_HighPass = 2,
|
||||
AF_BandPass = AF_LowPass | AF_HighPass
|
||||
};
|
||||
|
||||
|
||||
struct DirectParams {
|
||||
BiquadFilter LowPass;
|
||||
BiquadFilter HighPass;
|
||||
|
||||
NfcFilter NFCtrlFilter;
|
||||
|
||||
struct {
|
||||
HrtfFilter Old;
|
||||
HrtfFilter Target;
|
||||
alignas(16) std::array<float,HrtfHistoryLength> History;
|
||||
} Hrtf;
|
||||
|
||||
struct {
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Current;
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Target;
|
||||
} Gains;
|
||||
};
|
||||
|
||||
struct SendParams {
|
||||
BiquadFilter LowPass;
|
||||
BiquadFilter HighPass;
|
||||
|
||||
struct {
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Current;
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Target;
|
||||
} Gains;
|
||||
};
|
||||
|
||||
|
||||
struct VoiceBufferItem {
|
||||
std::atomic<VoiceBufferItem*> mNext{nullptr};
|
||||
|
||||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
uint mSampleLen{0u};
|
||||
uint mLoopStart{0u};
|
||||
uint mLoopEnd{0u};
|
||||
|
||||
al::byte *mSamples{nullptr};
|
||||
};
|
||||
|
||||
|
||||
struct VoiceProps {
|
||||
float Pitch;
|
||||
float Gain;
|
||||
float OuterGain;
|
||||
float MinGain;
|
||||
float MaxGain;
|
||||
float InnerAngle;
|
||||
float OuterAngle;
|
||||
float RefDistance;
|
||||
float MaxDistance;
|
||||
float RolloffFactor;
|
||||
std::array<float,3> Position;
|
||||
std::array<float,3> Velocity;
|
||||
std::array<float,3> Direction;
|
||||
std::array<float,3> OrientAt;
|
||||
std::array<float,3> OrientUp;
|
||||
bool HeadRelative;
|
||||
DistanceModel mDistanceModel;
|
||||
Resampler mResampler;
|
||||
DirectMode DirectChannels;
|
||||
SpatializeMode mSpatializeMode;
|
||||
|
||||
bool DryGainHFAuto;
|
||||
bool WetGainAuto;
|
||||
bool WetGainHFAuto;
|
||||
float OuterGainHF;
|
||||
|
||||
float AirAbsorptionFactor;
|
||||
float RoomRolloffFactor;
|
||||
float DopplerFactor;
|
||||
|
||||
std::array<float,2> StereoPan;
|
||||
|
||||
float Radius;
|
||||
float EnhWidth;
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
} Direct;
|
||||
struct SendData {
|
||||
EffectSlot *Slot;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
} Send[MAX_SENDS];
|
||||
};
|
||||
|
||||
struct VoicePropsItem : public VoiceProps {
|
||||
std::atomic<VoicePropsItem*> next{nullptr};
|
||||
|
||||
DEF_NEWDEL(VoicePropsItem)
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
VoiceIsStatic,
|
||||
VoiceIsCallback,
|
||||
VoiceIsAmbisonic,
|
||||
VoiceCallbackStopped,
|
||||
VoiceIsFading,
|
||||
VoiceHasHrtf,
|
||||
VoiceHasNfc,
|
||||
|
||||
VoiceFlagCount
|
||||
};
|
||||
|
||||
struct Voice {
|
||||
enum State {
|
||||
Stopped,
|
||||
Playing,
|
||||
Stopping,
|
||||
Pending
|
||||
};
|
||||
|
||||
std::atomic<VoicePropsItem*> mUpdate{nullptr};
|
||||
|
||||
VoiceProps mProps;
|
||||
|
||||
std::atomic<uint> mSourceID{0u};
|
||||
std::atomic<State> mPlayState{Stopped};
|
||||
std::atomic<bool> mPendingChange{false};
|
||||
|
||||
/**
|
||||
* Source offset in samples, relative to the currently playing buffer, NOT
|
||||
* the whole queue.
|
||||
*/
|
||||
std::atomic<uint> mPosition;
|
||||
/** Fractional (fixed-point) offset to the next sample. */
|
||||
std::atomic<uint> mPositionFrac;
|
||||
|
||||
/* Current buffer queue item being played. */
|
||||
std::atomic<VoiceBufferItem*> mCurrentBuffer;
|
||||
|
||||
/* Buffer queue item to loop to at end of queue (will be NULL for non-
|
||||
* looping voices).
|
||||
*/
|
||||
std::atomic<VoiceBufferItem*> mLoopBuffer;
|
||||
|
||||
/* Properties for the attached buffer(s). */
|
||||
FmtChannels mFmtChannels;
|
||||
FmtType mFmtType;
|
||||
uint mFrequency;
|
||||
uint mFrameStep; /**< In steps of the sample type size. */
|
||||
uint mFrameSize; /**< In bytes. */
|
||||
AmbiLayout mAmbiLayout;
|
||||
AmbiScaling mAmbiScaling;
|
||||
uint mAmbiOrder;
|
||||
|
||||
std::unique_ptr<UhjDecoder> mDecoder;
|
||||
UhjDecoder::DecoderFunc mDecoderFunc{};
|
||||
|
||||
/** Current target parameters used for mixing. */
|
||||
uint mStep{0};
|
||||
|
||||
ResamplerFunc mResampler;
|
||||
|
||||
InterpState mResampleState;
|
||||
|
||||
std::bitset<VoiceFlagCount> mFlags{};
|
||||
uint mNumCallbackSamples{0};
|
||||
|
||||
struct TargetData {
|
||||
int FilterType;
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
TargetData mDirect;
|
||||
std::array<TargetData,MAX_SENDS> mSend;
|
||||
|
||||
/* The first MaxResamplerPadding/2 elements are the sample history from the
|
||||
* previous mix, with an additional MaxResamplerPadding/2 elements that are
|
||||
* now current (which may be overwritten if the buffer data is still
|
||||
* available).
|
||||
*/
|
||||
using HistoryLine = std::array<float,MaxResamplerPadding>;
|
||||
al::vector<HistoryLine,16> mPrevSamples{2};
|
||||
|
||||
struct ChannelData {
|
||||
float mAmbiHFScale, mAmbiLFScale;
|
||||
BandSplitter mAmbiSplitter;
|
||||
|
||||
DirectParams mDryParams;
|
||||
std::array<SendParams,MAX_SENDS> mWetParams;
|
||||
};
|
||||
al::vector<ChannelData> mChans{2};
|
||||
|
||||
Voice() = default;
|
||||
~Voice() = default;
|
||||
|
||||
Voice(const Voice&) = delete;
|
||||
Voice& operator=(const Voice&) = delete;
|
||||
|
||||
void mix(const State vstate, ContextBase *Context, const uint SamplesToDo);
|
||||
|
||||
void prepare(DeviceBase *device);
|
||||
|
||||
static void InitMixer(al::optional<std::string> resampler);
|
||||
|
||||
DEF_NEWDEL(Voice)
|
||||
};
|
||||
|
||||
extern Resampler ResamplerDefault;
|
||||
|
||||
#endif /* CORE_VOICE_H */
|
||||
31
Engine/lib/openal-soft/core/voice_change.h
Normal file
31
Engine/lib/openal-soft/core/voice_change.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef VOICE_CHANGE_H
|
||||
#define VOICE_CHANGE_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct Voice;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
enum class VChangeState {
|
||||
Reset,
|
||||
Stop,
|
||||
Play,
|
||||
Pause,
|
||||
Restart
|
||||
};
|
||||
struct VoiceChange {
|
||||
Voice *mOldVoice{nullptr};
|
||||
Voice *mVoice{nullptr};
|
||||
uint mSourceID{0};
|
||||
VChangeState mState{};
|
||||
|
||||
std::atomic<VoiceChange*> mNext{nullptr};
|
||||
|
||||
DEF_NEWDEL(VoiceChange)
|
||||
};
|
||||
|
||||
#endif /* VOICE_CHANGE_H */
|
||||
Loading…
Add table
Add a link
Reference in a new issue