* 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:
Robert MacGregor 2022-05-30 16:32:45 -04:00
parent e071f1d901
commit 7380161054
234 changed files with 30864 additions and 7523 deletions

View file

@ -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;

View file

@ -46,6 +46,8 @@ struct AmbDecConf {
float HFOrderGain[MaxAmbiOrder+1]{};
CoeffArray *HFMatrix;
~AmbDecConf();
al::optional<std::string> load(const char *fname) noexcept;
};

View 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;
}

View file

@ -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 {

View 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

View 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));
}

View 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 */

View file

@ -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;

View file

@ -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 */

View file

@ -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)};

View 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;
}

View 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 */

View file

@ -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 */

View 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;
}
}

View 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 */

View 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
}
}
}

View 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 */

View 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

View 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 */

View file

@ -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";

View file

@ -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

View 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;
}

View 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 */

View 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 */

View 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;
}

View 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 */

View file

@ -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};

View file

@ -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.

View file

@ -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;

View file

@ -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);
}

View file

@ -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>;

View file

@ -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)

View file

@ -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;

View 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 */

View 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, &param);
if(err == EINVAL)
#endif
err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
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

View 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 */

File diff suppressed because it is too large Load diff

View 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 */

View file

@ -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);

View file

@ -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__)

View file

@ -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

View file

@ -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 */

View 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);
}

View 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 */

View file

@ -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);

View file

@ -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{});
}

View file

@ -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{};

View file

@ -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)
{

View file

@ -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)};

View file

@ -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)};

View file

@ -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;

View file

@ -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;

View 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 */

View 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;
}

View 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

View file

@ -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];
}

View file

@ -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 */

View 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 */

View 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);
}
}

View 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 */

View 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 */