First Commit
This commit is contained in:
306
externals/openal-soft/core/ambdec.cpp
vendored
Normal file
306
externals/openal-soft/core/ambdec.cpp
vendored
Normal file
@ -0,0 +1,306 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ambdec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "albit.h"
|
||||
#include "alfstream.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_word(std::istream &f)
|
||||
{
|
||||
std::string ret;
|
||||
f >> ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_at_end(const std::string &buffer, std::size_t endpos)
|
||||
{
|
||||
while(endpos < buffer.length() && std::isspace(buffer[endpos]))
|
||||
++endpos;
|
||||
return !(endpos < buffer.length() && buffer[endpos] != '#');
|
||||
}
|
||||
|
||||
|
||||
enum class ReaderScope {
|
||||
Global,
|
||||
Speakers,
|
||||
LFMatrix,
|
||||
HFMatrix,
|
||||
};
|
||||
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,2,3)]]
|
||||
#else
|
||||
[[gnu::format(printf,2,3)]]
|
||||
#endif
|
||||
al::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
|
||||
{
|
||||
al::optional<std::string> ret;
|
||||
auto &str = ret.emplace();
|
||||
|
||||
str.resize(256);
|
||||
int printed{std::snprintf(const_cast<char*>(str.data()), str.length(), "Line %zu: ", linenum)};
|
||||
if(printed < 0) printed = 0;
|
||||
auto plen = std::min(static_cast<size_t>(printed), str.length());
|
||||
|
||||
std::va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)};
|
||||
if(msglen >= 0 && static_cast<size_t>(msglen) >= str.size()-plen)
|
||||
{
|
||||
str.resize(static_cast<size_t>(msglen) + plen + 1u);
|
||||
std::vsnprintf(&str[plen], str.size()-plen, fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AmbDecConf::~AmbDecConf() = default;
|
||||
|
||||
|
||||
al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
||||
{
|
||||
al::ifstream f{fname};
|
||||
if(!f.is_open())
|
||||
return std::string("Failed to open file \"")+fname+"\"";
|
||||
|
||||
ReaderScope scope{ReaderScope::Global};
|
||||
size_t speaker_pos{0};
|
||||
size_t lfmatrix_pos{0};
|
||||
size_t hfmatrix_pos{0};
|
||||
size_t linenum{0};
|
||||
|
||||
std::string buffer;
|
||||
while(f.good() && std::getline(f, buffer))
|
||||
{
|
||||
++linenum;
|
||||
|
||||
std::istringstream istr{buffer};
|
||||
std::string command{read_word(istr)};
|
||||
if(command.empty() || command[0] == '#')
|
||||
continue;
|
||||
|
||||
if(command == "/}")
|
||||
{
|
||||
if(scope == ReaderScope::Global)
|
||||
return make_error(linenum, "Unexpected /} in global scope");
|
||||
scope = ReaderScope::Global;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(scope == ReaderScope::Speakers)
|
||||
{
|
||||
if(command == "add_spkr")
|
||||
{
|
||||
if(speaker_pos == NumSpeakers)
|
||||
return make_error(linenum, "Too many speakers specified");
|
||||
|
||||
AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++];
|
||||
istr >> spkr.Name;
|
||||
istr >> spkr.Distance;
|
||||
istr >> spkr.Azimuth;
|
||||
istr >> spkr.Elevation;
|
||||
istr >> spkr.Connection;
|
||||
}
|
||||
else
|
||||
return make_error(linenum, "Unexpected speakers command: %s", command.c_str());
|
||||
}
|
||||
else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix)
|
||||
{
|
||||
auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain;
|
||||
auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix;
|
||||
auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos;
|
||||
|
||||
if(command == "order_gain")
|
||||
{
|
||||
size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u};
|
||||
std::size_t curgain{0u};
|
||||
float value{};
|
||||
while(toread)
|
||||
{
|
||||
--toread;
|
||||
istr >> value;
|
||||
if(curgain < al::size(gains))
|
||||
gains[curgain++] = value;
|
||||
}
|
||||
}
|
||||
else if(command == "add_row")
|
||||
{
|
||||
if(pos == NumSpeakers)
|
||||
return make_error(linenum, "Too many matrix rows specified");
|
||||
|
||||
unsigned int mask{ChanMask};
|
||||
|
||||
AmbDecConf::CoeffArray &mtxrow = matrix[pos++];
|
||||
mtxrow.fill(0.0f);
|
||||
|
||||
float value{};
|
||||
while(mask)
|
||||
{
|
||||
auto idx = static_cast<unsigned>(al::countr_zero(mask));
|
||||
mask &= ~(1u << idx);
|
||||
|
||||
istr >> value;
|
||||
if(idx < mtxrow.size())
|
||||
mtxrow[idx] = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
return make_error(linenum, "Unexpected matrix command: %s", command.c_str());
|
||||
}
|
||||
// Global scope commands
|
||||
else if(command == "/description")
|
||||
{
|
||||
while(istr.good() && std::isspace(istr.peek()))
|
||||
istr.ignore();
|
||||
std::getline(istr, Description);
|
||||
while(!Description.empty() && std::isspace(Description.back()))
|
||||
Description.pop_back();
|
||||
}
|
||||
else if(command == "/version")
|
||||
{
|
||||
if(Version)
|
||||
return make_error(linenum, "Duplicate version definition");
|
||||
istr >> Version;
|
||||
if(Version != 3)
|
||||
return make_error(linenum, "Unsupported version: %d", Version);
|
||||
}
|
||||
else if(command == "/dec/chan_mask")
|
||||
{
|
||||
if(ChanMask)
|
||||
return make_error(linenum, "Duplicate chan_mask definition");
|
||||
istr >> std::hex >> ChanMask >> std::dec;
|
||||
|
||||
if(!ChanMask || ChanMask > Ambi4OrderMask)
|
||||
return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask);
|
||||
if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
|
||||
return make_error(linenum, "FuMa not compatible with over third-order");
|
||||
}
|
||||
else if(command == "/dec/freq_bands")
|
||||
{
|
||||
if(FreqBands)
|
||||
return make_error(linenum, "Duplicate freq_bands");
|
||||
istr >> FreqBands;
|
||||
if(FreqBands != 1 && FreqBands != 2)
|
||||
return make_error(linenum, "Invalid freq_bands: %u", FreqBands);
|
||||
}
|
||||
else if(command == "/dec/speakers")
|
||||
{
|
||||
if(NumSpeakers)
|
||||
return make_error(linenum, "Duplicate speakers");
|
||||
istr >> NumSpeakers;
|
||||
if(!NumSpeakers)
|
||||
return make_error(linenum, "Invalid speakers: %zu", NumSpeakers);
|
||||
Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers);
|
||||
}
|
||||
else if(command == "/dec/coeff_scale")
|
||||
{
|
||||
if(CoeffScale != AmbDecScale::Unset)
|
||||
return make_error(linenum, "Duplicate coeff_scale");
|
||||
|
||||
std::string scale{read_word(istr)};
|
||||
if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
|
||||
else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
|
||||
else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
|
||||
else
|
||||
return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str());
|
||||
|
||||
if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
|
||||
return make_error(linenum, "FuMa not compatible with over third-order");
|
||||
}
|
||||
else if(command == "/opt/xover_freq")
|
||||
{
|
||||
istr >> XOverFreq;
|
||||
}
|
||||
else if(command == "/opt/xover_ratio")
|
||||
{
|
||||
istr >> XOverRatio;
|
||||
}
|
||||
else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp"
|
||||
|| command == "/opt/delay_comp" || command == "/opt/level_comp")
|
||||
{
|
||||
/* Unused */
|
||||
read_word(istr);
|
||||
}
|
||||
else if(command == "/speakers/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
return make_error(linenum, "Speakers defined without a count");
|
||||
scope = ReaderScope::Speakers;
|
||||
}
|
||||
else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
return make_error(linenum, "Matrix defined without a speaker count");
|
||||
if(!ChanMask)
|
||||
return make_error(linenum, "Matrix defined without a channel mask");
|
||||
|
||||
if(!Matrix)
|
||||
{
|
||||
Matrix = std::make_unique<CoeffArray[]>(NumSpeakers * FreqBands);
|
||||
LFMatrix = Matrix.get();
|
||||
HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1);
|
||||
}
|
||||
|
||||
if(FreqBands == 1)
|
||||
{
|
||||
if(command != "/matrix/{")
|
||||
return make_error(linenum, "Unexpected \"%s\" for a single-band decoder",
|
||||
command.c_str());
|
||||
scope = ReaderScope::HFMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(command == "/lfmatrix/{")
|
||||
scope = ReaderScope::LFMatrix;
|
||||
else if(command == "/hfmatrix/{")
|
||||
scope = ReaderScope::HFMatrix;
|
||||
else
|
||||
return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder",
|
||||
command.c_str());
|
||||
}
|
||||
}
|
||||
else if(command == "/end")
|
||||
{
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str());
|
||||
|
||||
if(speaker_pos < NumSpeakers || hfmatrix_pos < NumSpeakers
|
||||
|| (FreqBands == 2 && lfmatrix_pos < NumSpeakers))
|
||||
return make_error(linenum, "Incomplete decoder definition");
|
||||
if(CoeffScale == AmbDecScale::Unset)
|
||||
return make_error(linenum, "No coefficient scaling defined");
|
||||
|
||||
return al::nullopt;
|
||||
}
|
||||
else
|
||||
return make_error(linenum, "Unexpected command: %s", command.c_str());
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str());
|
||||
buffer.clear();
|
||||
}
|
||||
return make_error(linenum, "Unexpected end of file");
|
||||
}
|
||||
55
externals/openal-soft/core/ambdec.h
vendored
Normal file
55
externals/openal-soft/core/ambdec.h
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef CORE_AMBDEC_H
|
||||
#define CORE_AMBDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "core/ambidefs.h"
|
||||
|
||||
/* Helpers to read .ambdec configuration files. */
|
||||
|
||||
enum class AmbDecScale {
|
||||
Unset,
|
||||
N3D,
|
||||
SN3D,
|
||||
FuMa,
|
||||
};
|
||||
struct AmbDecConf {
|
||||
std::string Description;
|
||||
int Version{0}; /* Must be 3 */
|
||||
|
||||
unsigned int ChanMask{0u};
|
||||
unsigned int FreqBands{0u}; /* Must be 1 or 2 */
|
||||
AmbDecScale CoeffScale{AmbDecScale::Unset};
|
||||
|
||||
float XOverFreq{0.0f};
|
||||
float XOverRatio{0.0f};
|
||||
|
||||
struct SpeakerConf {
|
||||
std::string Name;
|
||||
float Distance{0.0f};
|
||||
float Azimuth{0.0f};
|
||||
float Elevation{0.0f};
|
||||
std::string Connection;
|
||||
};
|
||||
size_t NumSpeakers{0};
|
||||
std::unique_ptr<SpeakerConf[]> Speakers;
|
||||
|
||||
using CoeffArray = std::array<float,MaxAmbiChannels>;
|
||||
std::unique_ptr<CoeffArray[]> Matrix;
|
||||
|
||||
/* Unused when FreqBands == 1 */
|
||||
float LFOrderGain[MaxAmbiOrder+1]{};
|
||||
CoeffArray *LFMatrix;
|
||||
|
||||
float HFOrderGain[MaxAmbiOrder+1]{};
|
||||
CoeffArray *HFMatrix;
|
||||
|
||||
~AmbDecConf();
|
||||
|
||||
al::optional<std::string> load(const char *fname) noexcept;
|
||||
};
|
||||
|
||||
#endif /* CORE_AMBDEC_H */
|
||||
308
externals/openal-soft/core/ambidefs.cpp
vendored
Normal file
308
externals/openal-soft/core/ambidefs.cpp
vendored
Normal file
@ -0,0 +1,308 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ambidefs.h"
|
||||
|
||||
#include "alnumbers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using AmbiChannelFloatArray = std::array<float,MaxAmbiChannels>;
|
||||
|
||||
constexpr auto inv_sqrt2f = static_cast<float>(1.0/al::numbers::sqrt2);
|
||||
constexpr auto inv_sqrt3f = static_cast<float>(1.0/al::numbers::sqrt3);
|
||||
|
||||
|
||||
/* These HF gains are derived from the same 32-point speaker array. The scale
|
||||
* factor between orders represents the same scale factors for any (regular)
|
||||
* speaker array decoder. e.g. Given a first-order source and second-order
|
||||
* output, applying an HF scale of HFScales[1][0] / HFScales[2][0] to channel 0
|
||||
* will result in that channel being subsequently decoded for second-order as
|
||||
* if it was a first-order decoder for that same speaker array.
|
||||
*/
|
||||
constexpr std::array<std::array<float,MaxAmbiOrder+1>,MaxAmbiOrder+1> HFScales{{
|
||||
{{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f }},
|
||||
/* 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f */
|
||||
}};
|
||||
|
||||
/* Same as above, but using a 10-point horizontal-only speaker array. Should
|
||||
* only be used when the device is mixing in 2D B-Format for horizontal-only
|
||||
* output.
|
||||
*/
|
||||
constexpr std::array<std::array<float,MaxAmbiOrder+1>,MaxAmbiOrder+1> HFScales2D{{
|
||||
{{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f }},
|
||||
/* 1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f */
|
||||
}};
|
||||
|
||||
|
||||
/* This calculates a first-order "upsampler" matrix. It combines a first-order
|
||||
* decoder matrix with a max-order encoder matrix, creating a matrix that
|
||||
* behaves as if the B-Format input signal is first decoded to a speaker array
|
||||
* at first-order, then those speaker feeds are encoded to a higher-order
|
||||
* signal. While not perfect, this should accurately encode a lower-order
|
||||
* signal into a higher-order signal.
|
||||
*/
|
||||
constexpr std::array<std::array<float,4>,8> FirstOrderDecoder{{
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,8> FirstOrderEncoder{{
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
}};
|
||||
static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order mismatch");
|
||||
|
||||
/* This calculates a 2D first-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,4>,4> FirstOrder2DDecoder{{
|
||||
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,4> FirstOrder2DEncoder{{
|
||||
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f),
|
||||
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f),
|
||||
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f),
|
||||
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f),
|
||||
}};
|
||||
static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a second-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a slightly more dense speaker array suitable for second-
|
||||
* order content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,9>,12> SecondOrderDecoder{{
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,12> SecondOrderEncoder{{
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, 8.506508084e-01f),
|
||||
CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f),
|
||||
CalcAmbiCoeffs(-5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, 8.506508084e-01f),
|
||||
CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f),
|
||||
CalcAmbiCoeffs( 5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, -8.506508084e-01f),
|
||||
CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f),
|
||||
CalcAmbiCoeffs( 5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, -8.506508084e-01f),
|
||||
CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f),
|
||||
CalcAmbiCoeffs(-5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f),
|
||||
}};
|
||||
static_assert(SecondOrderDecoder.size() == SecondOrderEncoder.size(), "Second-order mismatch");
|
||||
|
||||
/* This calculates a 2D second-order "upsampler" matrix. Same as the second-
|
||||
* order matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,9>,6> SecondOrder2DDecoder{{
|
||||
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
|
||||
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
|
||||
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,6> SecondOrder2DEncoder{{
|
||||
CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f),
|
||||
CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f),
|
||||
CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f),
|
||||
CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f),
|
||||
}};
|
||||
static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(),
|
||||
"Second-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a third-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a more dense speaker array suitable for third-order
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,16>,20> ThirdOrderDecoder{{
|
||||
{{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,20> ThirdOrderEncoder{{
|
||||
CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.35682208976f, -0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, 0.35682208976f),
|
||||
CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, -0.35682208976f),
|
||||
CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, 0.35682208976f),
|
||||
CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, -0.35682208976f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, 0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, -0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, 0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, -0.93417235897f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
}};
|
||||
static_assert(ThirdOrderDecoder.size() == ThirdOrderEncoder.size(), "Third-order mismatch");
|
||||
|
||||
/* This calculates a 2D third-order "upsampler" matrix. Same as the third-order
|
||||
* matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,16>,8> ThirdOrder2DDecoder{{
|
||||
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,8> ThirdOrder2DEncoder{{
|
||||
CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f),
|
||||
CalcAmbiCoeffs(-0.38268343237f, 0.0f, -0.92387953251f),
|
||||
CalcAmbiCoeffs( 0.38268343237f, 0.0f, -0.92387953251f),
|
||||
CalcAmbiCoeffs( 0.92387953251f, 0.0f, -0.38268343237f),
|
||||
CalcAmbiCoeffs( 0.92387953251f, 0.0f, 0.38268343237f),
|
||||
CalcAmbiCoeffs( 0.38268343237f, 0.0f, 0.92387953251f),
|
||||
}};
|
||||
static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a 2D fourth-order "upsampler" matrix. There is no 3D fourth-
|
||||
* order upsampler since fourth-order is the max order we'll be supporting for
|
||||
* the foreseeable future. This is only necessary for mixing horizontal-only
|
||||
* fourth-order content to 3D.
|
||||
*/
|
||||
constexpr std::array<std::array<float,25>,10> FourthOrder2DDecoder{{
|
||||
{{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }},
|
||||
{{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }},
|
||||
{{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,10> FourthOrder2DEncoder{{
|
||||
CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f),
|
||||
CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f),
|
||||
CalcAmbiCoeffs( 1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f),
|
||||
CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f),
|
||||
CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f),
|
||||
CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f),
|
||||
CalcAmbiCoeffs(-1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f),
|
||||
CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f),
|
||||
}};
|
||||
static_assert(FourthOrder2DDecoder.size() == FourthOrder2DEncoder.size(), "Fourth-order 2D mismatch");
|
||||
|
||||
|
||||
template<size_t N, size_t M>
|
||||
auto CalcAmbiUpsampler(const std::array<std::array<float,N>,M> &decoder,
|
||||
const std::array<AmbiChannelFloatArray,M> &encoder)
|
||||
{
|
||||
std::array<AmbiChannelFloatArray,N> res{};
|
||||
|
||||
for(size_t i{0};i < decoder[0].size();++i)
|
||||
{
|
||||
for(size_t j{0};j < encoder[0].size();++j)
|
||||
{
|
||||
double sum{0.0};
|
||||
for(size_t k{0};k < decoder.size();++k)
|
||||
sum += double{decoder[k][i]} * encoder[k][j];
|
||||
res[i][j] = static_cast<float>(sum);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiOrder+1> AmbiScale::GetHFOrderScales(const uint src_order,
|
||||
const uint dev_order, const bool horizontalOnly) noexcept
|
||||
{
|
||||
std::array<float,MaxAmbiOrder+1> res{};
|
||||
|
||||
if(!horizontalOnly)
|
||||
{
|
||||
for(size_t i{0};i < MaxAmbiOrder+1;++i)
|
||||
res[i] = HFScales[src_order][i] / HFScales[dev_order][i];
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t i{0};i < MaxAmbiOrder+1;++i)
|
||||
res[i] = HFScales2D[src_order][i] / HFScales2D[dev_order][i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
250
externals/openal-soft/core/ambidefs.h
vendored
Normal file
250
externals/openal-soft/core/ambidefs.h
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
#ifndef CORE_AMBIDEFS_H
|
||||
#define CORE_AMBIDEFS_H
|
||||
|
||||
#include <array>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "alnumbers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* The maximum number of Ambisonics channels. For a given order (o), the size
|
||||
* needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second-
|
||||
* order has 9, third-order has 16, and fourth-order has 25.
|
||||
*/
|
||||
constexpr uint8_t MaxAmbiOrder{3};
|
||||
constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
|
||||
{ return (order+1) * (order+1); }
|
||||
constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)};
|
||||
|
||||
/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up
|
||||
* to 4th order, which is the highest order a 32-bit mask value can specify (a
|
||||
* 64-bit mask could handle up to 7th order).
|
||||
*/
|
||||
constexpr uint Ambi0OrderMask{0x00000001};
|
||||
constexpr uint Ambi1OrderMask{0x0000000f};
|
||||
constexpr uint Ambi2OrderMask{0x000001ff};
|
||||
constexpr uint Ambi3OrderMask{0x0000ffff};
|
||||
constexpr uint Ambi4OrderMask{0x01ffffff};
|
||||
|
||||
/* A bitmask of ambisonic channels with height information. If none of these
|
||||
* channels are used/needed, there's no height (e.g. with most surround sound
|
||||
* speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc.
|
||||
*/
|
||||
constexpr uint AmbiPeriphonicMask{0xfe7ce4};
|
||||
|
||||
/* The maximum number of ambisonic channels for 2D (non-periphonic)
|
||||
* representation. This is 2 per each order above zero-order, plus 1 for zero-
|
||||
* order. Or simply, o*2 + 1.
|
||||
*/
|
||||
constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept
|
||||
{ return order*2 + 1; }
|
||||
constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)};
|
||||
|
||||
|
||||
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
|
||||
* coefficients should be divided by these values to get proper scalings.
|
||||
*/
|
||||
struct AmbiScale {
|
||||
static auto& FromN3D() noexcept
|
||||
{
|
||||
static constexpr const std::array<float,MaxAmbiChannels> ret{{
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& FromSN3D() noexcept
|
||||
{
|
||||
static constexpr const std::array<float,MaxAmbiChannels> ret{{
|
||||
1.000000000f, /* ACN 0, sqrt(1) */
|
||||
1.732050808f, /* ACN 1, sqrt(3) */
|
||||
1.732050808f, /* ACN 2, sqrt(3) */
|
||||
1.732050808f, /* ACN 3, sqrt(3) */
|
||||
2.236067978f, /* ACN 4, sqrt(5) */
|
||||
2.236067978f, /* ACN 5, sqrt(5) */
|
||||
2.236067978f, /* ACN 6, sqrt(5) */
|
||||
2.236067978f, /* ACN 7, sqrt(5) */
|
||||
2.236067978f, /* ACN 8, sqrt(5) */
|
||||
2.645751311f, /* ACN 9, sqrt(7) */
|
||||
2.645751311f, /* ACN 10, sqrt(7) */
|
||||
2.645751311f, /* ACN 11, sqrt(7) */
|
||||
2.645751311f, /* ACN 12, sqrt(7) */
|
||||
2.645751311f, /* ACN 13, sqrt(7) */
|
||||
2.645751311f, /* ACN 14, sqrt(7) */
|
||||
2.645751311f, /* ACN 15, sqrt(7) */
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& FromFuMa() noexcept
|
||||
{
|
||||
static constexpr const std::array<float,MaxAmbiChannels> ret{{
|
||||
1.414213562f, /* ACN 0 (W), sqrt(2) */
|
||||
1.732050808f, /* ACN 1 (Y), sqrt(3) */
|
||||
1.732050808f, /* ACN 2 (Z), sqrt(3) */
|
||||
1.732050808f, /* ACN 3 (X), sqrt(3) */
|
||||
1.936491673f, /* ACN 4 (V), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 5 (T), sqrt(15)/2 */
|
||||
2.236067978f, /* ACN 6 (R), sqrt(5) */
|
||||
1.936491673f, /* ACN 7 (S), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 8 (U), sqrt(15)/2 */
|
||||
2.091650066f, /* ACN 9 (Q), sqrt(35/8) */
|
||||
1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
|
||||
2.231093404f, /* ACN 11 (M), sqrt(224/45) */
|
||||
2.645751311f, /* ACN 12 (K), sqrt(7) */
|
||||
2.231093404f, /* ACN 13 (L), sqrt(224/45) */
|
||||
1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
|
||||
2.091650066f, /* ACN 15 (P), sqrt(35/8) */
|
||||
}};
|
||||
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 src_order,
|
||||
const uint dev_order, const bool horizontalOnly) noexcept;
|
||||
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,25> FourthOrder2DUp;
|
||||
};
|
||||
|
||||
struct AmbiIndex {
|
||||
static auto& FromFuMa() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbiChannels> ret{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
2, /* Z */
|
||||
6, /* R */
|
||||
7, /* S */
|
||||
5, /* T */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
12, /* K */
|
||||
13, /* L */
|
||||
11, /* M */
|
||||
14, /* N */
|
||||
10, /* O */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& FromFuMa2D() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbi2DChannels> ret{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
|
||||
static auto& FromACN() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbiChannels> ret{{
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& FromACN2D() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbi2DChannels> ret{{
|
||||
0, 1,3, 4,8, 9,15
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
|
||||
static auto& OrderFromChannel() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbiChannels> ret{{
|
||||
0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
static auto& OrderFrom2DChannel() noexcept
|
||||
{
|
||||
static constexpr const std::array<uint8_t,MaxAmbi2DChannels> ret{{
|
||||
0, 1,1, 2,2, 3,3,
|
||||
}};
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
constexpr auto CalcAmbiCoeffs(const float y, const float z, const float x)
|
||||
{
|
||||
const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z};
|
||||
|
||||
return std::array<float,MaxAmbiChannels>{{
|
||||
/* Zeroth-order */
|
||||
1.0f, /* ACN 0 = 1 */
|
||||
/* First-order */
|
||||
al::numbers::sqrt3_v<float> * y, /* ACN 1 = sqrt(3) * Y */
|
||||
al::numbers::sqrt3_v<float> * z, /* ACN 2 = sqrt(3) * Z */
|
||||
al::numbers::sqrt3_v<float> * x, /* ACN 3 = sqrt(3) * X */
|
||||
/* Second-order */
|
||||
3.872983346e+00f * xy, /* ACN 4 = sqrt(15) * X * Y */
|
||||
3.872983346e+00f * yz, /* ACN 5 = sqrt(15) * Y * Z */
|
||||
1.118033989e+00f * (3.0f*zz - 1.0f), /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
|
||||
3.872983346e+00f * xz, /* ACN 7 = sqrt(15) * X * Z */
|
||||
1.936491673e+00f * (xx - yy), /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
|
||||
/* Third-order */
|
||||
2.091650066e+00f * (y*(3.0f*xx - yy)), /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
|
||||
1.024695076e+01f * (z*xy), /* ACN 10 = sqrt(105) * Z * X * Y */
|
||||
1.620185175e+00f * (y*(5.0f*zz - 1.0f)), /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
|
||||
1.322875656e+00f * (z*(5.0f*zz - 3.0f)), /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
|
||||
1.620185175e+00f * (x*(5.0f*zz - 1.0f)), /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
|
||||
5.123475383e+00f * (z*(xx - yy)), /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
|
||||
2.091650066e+00f * (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) */
|
||||
}};
|
||||
}
|
||||
|
||||
#endif /* CORE_AMBIDEFS_H */
|
||||
55
externals/openal-soft/core/async_event.h
vendored
Normal file
55
externals/openal-soft/core/async_event.h
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef CORE_EVENT_H
|
||||
#define CORE_EVENT_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct EffectState;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
struct AsyncEvent {
|
||||
enum : uint {
|
||||
/* User event types. */
|
||||
SourceStateChange,
|
||||
BufferCompleted,
|
||||
Disconnected,
|
||||
UserEventCount,
|
||||
|
||||
/* Internal events, always processed. */
|
||||
ReleaseEffectState = 128,
|
||||
|
||||
/* End event thread processing. */
|
||||
KillThread,
|
||||
};
|
||||
|
||||
enum class SrcState {
|
||||
Reset,
|
||||
Stop,
|
||||
Play,
|
||||
Pause
|
||||
};
|
||||
|
||||
const uint EnumType;
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
uint id;
|
||||
SrcState state;
|
||||
} srcstate;
|
||||
struct {
|
||||
uint id;
|
||||
uint count;
|
||||
} bufcomp;
|
||||
struct {
|
||||
char msg[244];
|
||||
} disconnect;
|
||||
EffectState *mEffectState;
|
||||
} u{};
|
||||
|
||||
constexpr AsyncEvent(uint type) noexcept : EnumType{type} { }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
#endif
|
||||
170
externals/openal-soft/core/bformatdec.cpp
vendored
Normal file
170
externals/openal-soft/core/bformatdec.cpp
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
|
||||
#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.
|
||||
*/
|
||||
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[i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
side[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);
|
||||
|
||||
/* Include the decoded side signal with the direct side signal. */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
side[i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
|
||||
/* Get the decoded mid signal and band-split it. */
|
||||
std::transform(OutBuffer[lidx].cbegin(), OutBuffer[lidx].cbegin()+SamplesToDo,
|
||||
OutBuffer[ridx].cbegin(), mStablizer->Temp.begin(),
|
||||
[](const float l, const float r) noexcept { return l + r; });
|
||||
|
||||
mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(),
|
||||
mStablizer->MidLF.data());
|
||||
|
||||
/* Apply an all-pass to all channels to match the band-splitter's phase
|
||||
* shift. This is to keep the phase synchronized between the existing
|
||||
* signal and the split mid signal.
|
||||
*/
|
||||
const size_t NumChannels{OutBuffer.size()};
|
||||
for(size_t i{0u};i < NumChannels;i++)
|
||||
{
|
||||
/* Skip the left and right channels, which are going to get overwritten,
|
||||
* and substitute the direct mid signal and direct+decoded side signal.
|
||||
*/
|
||||
if(i == lidx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({mid, SamplesToDo});
|
||||
else if(i == ridx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo});
|
||||
else
|
||||
mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo});
|
||||
}
|
||||
|
||||
/* 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++)
|
||||
{
|
||||
/* Add the direct mid signal to the processed mid signal so it can be
|
||||
* properly combined with the direct+decoded side signal.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<BFormatDec> BFormatDec::Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
const float xover_f0norm, std::unique_ptr<FrontStablizer> stablizer)
|
||||
{
|
||||
return std::make_unique<BFormatDec>(inchans, coeffs, coeffslf, xover_f0norm,
|
||||
std::move(stablizer));
|
||||
}
|
||||
71
externals/openal-soft/core/bformatdec.h
vendored
Normal file
71
externals/openal-soft/core/bformatdec.h
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef CORE_BFORMATDEC_H
|
||||
#define CORE_BFORMATDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct FrontStablizer;
|
||||
|
||||
|
||||
using ChannelDec = std::array<float,MaxAmbiChannels>;
|
||||
|
||||
class BFormatDec {
|
||||
static constexpr size_t sHFBand{0};
|
||||
static constexpr size_t sLFBand{1};
|
||||
static constexpr size_t sNumBands{2};
|
||||
|
||||
struct ChannelDecoder {
|
||||
union MatrixU {
|
||||
float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
|
||||
float Single[MAX_OUTPUT_CHANNELS];
|
||||
} mGains{};
|
||||
|
||||
/* NOTE: BandSplitter filter is unused with single-band decoding. */
|
||||
BandSplitter mXOver;
|
||||
};
|
||||
|
||||
alignas(16) std::array<FloatBufferLine,2> mSamples;
|
||||
|
||||
const std::unique_ptr<FrontStablizer> mStablizer;
|
||||
const bool mDualBand{false};
|
||||
|
||||
/* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather
|
||||
* small and only a few are needed (3, 4, 5, 7, typically). But that can
|
||||
* only be used in a standard layout struct, and a std::unique_ptr member
|
||||
* (mStablizer) causes GCC and Clang to warn it's not.
|
||||
*/
|
||||
al::vector<ChannelDecoder> mChannelDec;
|
||||
|
||||
public:
|
||||
BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
|
||||
const al::span<const ChannelDec> coeffslf, const float xover_f0norm,
|
||||
std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
bool hasStablizer() const noexcept { return mStablizer != nullptr; }
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels. */
|
||||
void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels with stablization. */
|
||||
void processStablize(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
static std::unique_ptr<BFormatDec> Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
const float xover_f0norm, std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
DEF_NEWDEL(BFormatDec)
|
||||
};
|
||||
|
||||
#endif /* CORE_BFORMATDEC_H */
|
||||
183
externals/openal-soft/core/bs2b.cpp
vendored
Normal file
183
externals/openal-soft/core/bs2b.cpp
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
/*-
|
||||
* Copyright (c) 2005 Boris Mikhaylov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "bs2b.h"
|
||||
|
||||
|
||||
/* Set up all data. */
|
||||
static void init(struct bs2b *bs2b)
|
||||
{
|
||||
float Fc_lo, Fc_hi;
|
||||
float G_lo, G_hi;
|
||||
float x, g;
|
||||
|
||||
switch(bs2b->level)
|
||||
{
|
||||
case BS2B_LOW_CLEVEL: /* Low crossfeed level */
|
||||
Fc_lo = 360.0f;
|
||||
Fc_hi = 501.0f;
|
||||
G_lo = 0.398107170553497f;
|
||||
G_hi = 0.205671765275719f;
|
||||
break;
|
||||
|
||||
case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */
|
||||
Fc_lo = 500.0f;
|
||||
Fc_hi = 711.0f;
|
||||
G_lo = 0.459726988530872f;
|
||||
G_hi = 0.228208484414988f;
|
||||
break;
|
||||
|
||||
case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */
|
||||
Fc_lo = 700.0f;
|
||||
Fc_hi = 1021.0f;
|
||||
G_lo = 0.530884444230988f;
|
||||
G_hi = 0.250105790667544f;
|
||||
break;
|
||||
|
||||
case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */
|
||||
Fc_lo = 360.0f;
|
||||
Fc_hi = 494.0f;
|
||||
G_lo = 0.316227766016838f;
|
||||
G_hi = 0.168236228897329f;
|
||||
break;
|
||||
|
||||
case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */
|
||||
Fc_lo = 500.0f;
|
||||
Fc_hi = 689.0f;
|
||||
G_lo = 0.354813389233575f;
|
||||
G_hi = 0.187169483835901f;
|
||||
break;
|
||||
|
||||
default: /* High easy crossfeed level */
|
||||
bs2b->level = BS2B_HIGH_ECLEVEL;
|
||||
|
||||
Fc_lo = 700.0f;
|
||||
Fc_hi = 975.0f;
|
||||
G_lo = 0.398107170553497f;
|
||||
G_hi = 0.205671765275719f;
|
||||
break;
|
||||
} /* switch */
|
||||
|
||||
g = 1.0f / (1.0f - G_hi + G_lo);
|
||||
|
||||
/* $fc = $Fc / $s;
|
||||
* $d = 1 / 2 / pi / $fc;
|
||||
* $x = exp(-1 / $d);
|
||||
*/
|
||||
x = std::exp(-al::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::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;
|
||||
} /* init */
|
||||
|
||||
|
||||
/* Exported functions.
|
||||
* See descriptions in "bs2b.h"
|
||||
*/
|
||||
|
||||
void bs2b_set_params(struct bs2b *bs2b, int level, int srate)
|
||||
{
|
||||
if(srate <= 0) srate = 1;
|
||||
|
||||
bs2b->level = level;
|
||||
bs2b->srate = srate;
|
||||
init(bs2b);
|
||||
} /* bs2b_set_params */
|
||||
|
||||
int bs2b_get_level(struct bs2b *bs2b)
|
||||
{
|
||||
return bs2b->level;
|
||||
} /* bs2b_get_level */
|
||||
|
||||
int bs2b_get_srate(struct bs2b *bs2b)
|
||||
{
|
||||
return bs2b->srate;
|
||||
} /* bs2b_get_srate */
|
||||
|
||||
void bs2b_clear(struct bs2b *bs2b)
|
||||
{
|
||||
std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{});
|
||||
} /* bs2b_clear */
|
||||
|
||||
void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo)
|
||||
{
|
||||
const float a0_lo{bs2b->a0_lo};
|
||||
const float b1_lo{bs2b->b1_lo};
|
||||
const float a0_hi{bs2b->a0_hi};
|
||||
const float a1_hi{bs2b->a1_hi};
|
||||
const float b1_hi{bs2b->b1_hi};
|
||||
float lsamples[128][2];
|
||||
float rsamples[128][2];
|
||||
|
||||
for(size_t base{0};base < SamplesToDo;)
|
||||
{
|
||||
const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
|
||||
|
||||
/* Process left input */
|
||||
float z_lo{bs2b->history[0].lo};
|
||||
float z_hi{bs2b->history[0].hi};
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
lsamples[i][0] = a0_lo*Left[i] + z_lo;
|
||||
z_lo = b1_lo*lsamples[i][0];
|
||||
|
||||
lsamples[i][1] = a0_hi*Left[i] + z_hi;
|
||||
z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1];
|
||||
}
|
||||
bs2b->history[0].lo = z_lo;
|
||||
bs2b->history[0].hi = z_hi;
|
||||
|
||||
/* Process right input */
|
||||
z_lo = bs2b->history[1].lo;
|
||||
z_hi = bs2b->history[1].hi;
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
rsamples[i][0] = a0_lo*Right[i] + z_lo;
|
||||
z_lo = b1_lo*rsamples[i][0];
|
||||
|
||||
rsamples[i][1] = a0_hi*Right[i] + z_hi;
|
||||
z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1];
|
||||
}
|
||||
bs2b->history[1].lo = z_lo;
|
||||
bs2b->history[1].hi = z_hi;
|
||||
|
||||
/* Crossfeed */
|
||||
for(size_t i{0};i < todo;i++)
|
||||
*(Left++) = lsamples[i][1] + rsamples[i][0];
|
||||
for(size_t i{0};i < todo;i++)
|
||||
*(Right++) = rsamples[i][1] + lsamples[i][0];
|
||||
|
||||
base += todo;
|
||||
}
|
||||
} /* bs2b_cross_feed */
|
||||
89
externals/openal-soft/core/bs2b.h
vendored
Normal file
89
externals/openal-soft/core/bs2b.h
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*-
|
||||
* Copyright (c) 2005 Boris Mikhaylov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef CORE_BS2B_H
|
||||
#define CORE_BS2B_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
/* Number of crossfeed levels */
|
||||
#define BS2B_CLEVELS 3
|
||||
|
||||
/* Normal crossfeed levels */
|
||||
#define BS2B_HIGH_CLEVEL 3
|
||||
#define BS2B_MIDDLE_CLEVEL 2
|
||||
#define BS2B_LOW_CLEVEL 1
|
||||
|
||||
/* Easy crossfeed levels */
|
||||
#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS
|
||||
#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS
|
||||
#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS
|
||||
|
||||
/* Default crossfeed levels */
|
||||
#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL
|
||||
/* Default sample rate (Hz) */
|
||||
#define BS2B_DEFAULT_SRATE 44100
|
||||
|
||||
struct bs2b {
|
||||
int level; /* Crossfeed level */
|
||||
int srate; /* Sample rate (Hz) */
|
||||
|
||||
/* Lowpass IIR filter coefficients */
|
||||
float a0_lo;
|
||||
float b1_lo;
|
||||
|
||||
/* Highboost IIR filter coefficients */
|
||||
float a0_hi;
|
||||
float a1_hi;
|
||||
float b1_hi;
|
||||
|
||||
/* Buffer of filter history
|
||||
* [0] - first channel, [1] - second channel
|
||||
*/
|
||||
struct t_last_sample {
|
||||
float lo;
|
||||
float hi;
|
||||
} history[2];
|
||||
|
||||
DEF_NEWDEL(bs2b)
|
||||
};
|
||||
|
||||
/* Clear buffers and set new coefficients with new crossfeed level and sample
|
||||
* rate values.
|
||||
* level - crossfeed level of *LEVEL values.
|
||||
* srate - sample rate by Hz.
|
||||
*/
|
||||
void bs2b_set_params(bs2b *bs2b, int level, int srate);
|
||||
|
||||
/* Return current crossfeed level value */
|
||||
int bs2b_get_level(bs2b *bs2b);
|
||||
|
||||
/* Return current sample rate value */
|
||||
int bs2b_get_srate(bs2b *bs2b);
|
||||
|
||||
/* Clear buffer */
|
||||
void bs2b_clear(bs2b *bs2b);
|
||||
|
||||
void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo);
|
||||
|
||||
#endif /* CORE_BS2B_H */
|
||||
12
externals/openal-soft/core/bsinc_defs.h
vendored
Normal file
12
externals/openal-soft/core/bsinc_defs.h
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef CORE_BSINC_DEFS_H
|
||||
#define CORE_BSINC_DEFS_H
|
||||
|
||||
/* The number of distinct scale and phase intervals within the bsinc filter
|
||||
* tables.
|
||||
*/
|
||||
constexpr unsigned int BSincScaleBits{4};
|
||||
constexpr unsigned int BSincScaleCount{1 << BSincScaleBits};
|
||||
constexpr unsigned int BSincPhaseBits{5};
|
||||
constexpr unsigned int BSincPhaseCount{1 << BSincPhaseBits};
|
||||
|
||||
#endif /* CORE_BSINC_DEFS_H */
|
||||
295
externals/openal-soft/core/bsinc_tables.cpp
vendored
Normal file
295
externals/openal-soft/core/bsinc_tables.cpp
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
|
||||
#include "bsinc_tables.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
/* This is the normalized cardinal sine (sinc) function.
|
||||
*
|
||||
* sinc(x) = { 1, x = 0
|
||||
* { sin(pi x) / (pi x), otherwise.
|
||||
*/
|
||||
constexpr double Sinc(const double x)
|
||||
{
|
||||
constexpr double epsilon{std::numeric_limits<double>::epsilon()};
|
||||
if(!(x > epsilon || x < -epsilon))
|
||||
return 1.0;
|
||||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
* Kaiser window.
|
||||
*
|
||||
* 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) noexcept
|
||||
{
|
||||
/* Start at k=1 since k=0 is trivial. */
|
||||
const double x2{x / 2.0};
|
||||
double term{1.0};
|
||||
double sum{1.0};
|
||||
double last_sum{};
|
||||
int k{1};
|
||||
|
||||
/* Let the integration converge until the term of the sum is no longer
|
||||
* significant.
|
||||
*/
|
||||
do {
|
||||
const double y{x2 / k};
|
||||
++k;
|
||||
last_sum = sum;
|
||||
term *= y * y;
|
||||
sum += term;
|
||||
} while(sum != last_sum);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* Calculate a Kaiser window from the given beta value and a normalized k
|
||||
* [-1, 1].
|
||||
*
|
||||
* w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
|
||||
* { 0, elsewhere.
|
||||
*
|
||||
* Where k can be calculated as:
|
||||
*
|
||||
* k = i / l, where -l <= i <= l.
|
||||
*
|
||||
* or:
|
||||
*
|
||||
* k = 2 i / M - 1, where 0 <= i <= M.
|
||||
*/
|
||||
constexpr double Kaiser(const double beta, const double k, const double besseli_0_beta)
|
||||
{
|
||||
if(!(k >= -1.0 && k <= 1.0))
|
||||
return 0.0;
|
||||
return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
|
||||
}
|
||||
|
||||
/* Calculates the (normalized frequency) transition width of the Kaiser window.
|
||||
* Rejection is in dB.
|
||||
*/
|
||||
constexpr double CalcKaiserWidth(const double rejection, const uint order) noexcept
|
||||
{
|
||||
if(rejection > 21.19)
|
||||
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 / (al::numbers::pi*2.0 * order);
|
||||
}
|
||||
|
||||
/* Calculates the beta value of the Kaiser window. Rejection is in dB. */
|
||||
constexpr double CalcKaiserBeta(const double rejection)
|
||||
{
|
||||
if(rejection > 50.0)
|
||||
return 0.1102 * (rejection-8.7);
|
||||
else if(rejection >= 21.0)
|
||||
return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0));
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
struct BSincHeader {
|
||||
double width{};
|
||||
double beta{};
|
||||
double scaleBase{};
|
||||
double scaleRange{};
|
||||
double besseli_0_beta{};
|
||||
|
||||
uint a[BSincScaleCount]{};
|
||||
uint total_size{};
|
||||
|
||||
constexpr BSincHeader(uint Rejection, uint Order) noexcept
|
||||
{
|
||||
width = CalcKaiserWidth(Rejection, Order);
|
||||
beta = CalcKaiserBeta(Rejection);
|
||||
scaleBase = width / 2.0;
|
||||
scaleRange = 1.0 - scaleBase;
|
||||
besseli_0_beta = BesselI_0(beta);
|
||||
|
||||
uint num_points{Order+1};
|
||||
for(uint si{0};si < BSincScaleCount;++si)
|
||||
{
|
||||
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_};
|
||||
|
||||
a[si] = a_;
|
||||
total_size += 4 * BSincPhaseCount * ((m+3) & ~3u);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop
|
||||
* at nyquist. Each filter will scale up the order when downsampling, to 23rd
|
||||
* and 47th order respectively.
|
||||
*/
|
||||
constexpr BSincHeader bsinc12_hdr{60, 11};
|
||||
constexpr BSincHeader bsinc24_hdr{60, 23};
|
||||
|
||||
|
||||
/* NOTE: GCC 5 has an issue with BSincHeader objects being in an anonymous
|
||||
* 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_) : hdr{hdr_}
|
||||
{
|
||||
#else
|
||||
template<const BSincHeader &hdr>
|
||||
struct BSincFilterArray {
|
||||
alignas(16) std::array<float, hdr.total_size> mTable{};
|
||||
|
||||
BSincFilterArray()
|
||||
{
|
||||
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.
|
||||
*/
|
||||
for(uint si{0};si < BSincScaleCount;++si)
|
||||
{
|
||||
const uint m{hdr.a[si] * 2};
|
||||
const size_t o{(BSincPointsMax-m) / 2};
|
||||
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/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{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/l, hdr.besseli_0_beta) * cutoff *
|
||||
Sinc(cutoff*x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t idx{0};
|
||||
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)
|
||||
{
|
||||
for(size_t i{0};i < m;++i)
|
||||
mTable[idx++] = static_cast<float>(filter[si][pi][o+i]);
|
||||
|
||||
/* Linear interpolation between phases is simplified by pre-
|
||||
* calculating the delta (b - a) in: x = a + f (b - a)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
/* 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 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)
|
||||
{
|
||||
const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]};
|
||||
mTable[idx++] = static_cast<float>(scDelta);
|
||||
}
|
||||
|
||||
/* This last simplification is done to complete the bilinear
|
||||
* equation for the combination of phase and scale.
|
||||
*/
|
||||
for(size_t i{0};i < m;++i)
|
||||
{
|
||||
const double spDelta{(filter[si+1][pi+1][o+i] - filter[si+1][pi][o+i]) -
|
||||
(filter[si][pi+1][o+i] - filter[si][pi][o+i])};
|
||||
mTable[idx++] = static_cast<float>(spDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
const BSincFilterArray<bsinc12_hdr.total_size> bsinc12_filter{bsinc12_hdr};
|
||||
const BSincFilterArray<bsinc24_hdr.total_size> bsinc24_filter{bsinc24_hdr};
|
||||
#else
|
||||
const BSincFilterArray<bsinc12_hdr> bsinc12_filter{};
|
||||
const BSincFilterArray<bsinc24_hdr> bsinc24_filter{};
|
||||
#endif
|
||||
|
||||
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)
|
||||
ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u;
|
||||
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 = filter.getTable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)};
|
||||
const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)};
|
||||
17
externals/openal-soft/core/bsinc_tables.h
vendored
Normal file
17
externals/openal-soft/core/bsinc_tables.h
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef CORE_BSINC_TABLES_H
|
||||
#define CORE_BSINC_TABLES_H
|
||||
|
||||
#include "bsinc_defs.h"
|
||||
|
||||
|
||||
struct BSincTable {
|
||||
float scaleBase, scaleRange;
|
||||
unsigned int m[BSincScaleCount];
|
||||
unsigned int filterOffset[BSincScaleCount];
|
||||
const float *Tab;
|
||||
};
|
||||
|
||||
extern const BSincTable gBSinc12;
|
||||
extern const BSincTable gBSinc24;
|
||||
|
||||
#endif /* CORE_BSINC_TABLES_H */
|
||||
81
externals/openal-soft/core/buffer_storage.cpp
vendored
Normal file
81
externals/openal-soft/core/buffer_storage.cpp
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "buffer_storage.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
const char *NameFromFormat(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return "UInt8";
|
||||
case FmtShort: return "Int16";
|
||||
case FmtFloat: return "Float";
|
||||
case FmtDouble: return "Double";
|
||||
case FmtMulaw: return "muLaw";
|
||||
case FmtAlaw: return "aLaw";
|
||||
case FmtIMA4: return "IMA4 ADPCM";
|
||||
case FmtMSADPCM: return "MS ADPCM";
|
||||
}
|
||||
return "<internal error>";
|
||||
}
|
||||
|
||||
const char *NameFromFormat(FmtChannels channels) noexcept
|
||||
{
|
||||
switch(channels)
|
||||
{
|
||||
case FmtMono: return "Mono";
|
||||
case FmtStereo: return "Stereo";
|
||||
case FmtRear: return "Rear";
|
||||
case FmtQuad: return "Quadraphonic";
|
||||
case FmtX51: return "Surround 5.1";
|
||||
case FmtX61: return "Surround 6.1";
|
||||
case FmtX71: return "Surround 7.1";
|
||||
case FmtBFormat2D: return "B-Format 2D";
|
||||
case FmtBFormat3D: return "B-Format 3D";
|
||||
case FmtUHJ2: return "UHJ2";
|
||||
case FmtUHJ3: return "UHJ3";
|
||||
case FmtUHJ4: return "UHJ4";
|
||||
case FmtSuperStereo: return "Super Stereo";
|
||||
}
|
||||
return "<internal error>";
|
||||
}
|
||||
|
||||
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);
|
||||
case FmtIMA4: break;
|
||||
case FmtMSADPCM: break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
115
externals/openal-soft/core/buffer_storage.h
vendored
Normal file
115
externals/openal-soft/core/buffer_storage.h
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
#ifndef CORE_BUFFER_STORAGE_H
|
||||
#define CORE_BUFFER_STORAGE_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType : unsigned char {
|
||||
FmtUByte,
|
||||
FmtShort,
|
||||
FmtFloat,
|
||||
FmtDouble,
|
||||
FmtMulaw,
|
||||
FmtAlaw,
|
||||
FmtIMA4,
|
||||
FmtMSADPCM,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
const char *NameFromFormat(FmtType type) noexcept;
|
||||
const char *NameFromFormat(FmtChannels channels) noexcept;
|
||||
|
||||
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};
|
||||
|
||||
al::span<al::byte> mData;
|
||||
|
||||
uint mSampleRate{0u};
|
||||
FmtChannels mChannels{FmtMono};
|
||||
FmtType mType{FmtShort};
|
||||
uint mSampleLen{0u};
|
||||
uint mBlockAlign{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 uint blockSizeFromFmt() const noexcept
|
||||
{
|
||||
if(mType == FmtIMA4) return ((mBlockAlign-1)/2 + 4) * channelsFromFmt();
|
||||
if(mType == FmtMSADPCM) return ((mBlockAlign-2)/2 + 7) * channelsFromFmt();
|
||||
return frameSizeFromFmt();
|
||||
};
|
||||
|
||||
inline bool isBFormat() const noexcept { return IsBFormat(mChannels); }
|
||||
};
|
||||
|
||||
#endif /* CORE_BUFFER_STORAGE_H */
|
||||
17
externals/openal-soft/core/bufferline.h
vendored
Normal file
17
externals/openal-soft/core/bufferline.h
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef CORE_BUFFERLINE_H
|
||||
#define CORE_BUFFERLINE_H
|
||||
|
||||
#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.
|
||||
*/
|
||||
constexpr int BufferLineSize{1024};
|
||||
|
||||
using FloatBufferLine = std::array<float,BufferLineSize>;
|
||||
using FloatBufferSpan = al::span<float,BufferLineSize>;
|
||||
|
||||
#endif /* CORE_BUFFERLINE_H */
|
||||
164
externals/openal-soft/core/context.cpp
vendored
Normal file
164
externals/openal-soft/core/context.cpp
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cassert>
|
||||
#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"
|
||||
|
||||
|
||||
#ifdef __cpp_lib_atomic_is_always_lock_free
|
||||
static_assert(std::atomic<ContextBase::AsyncEventBitset>::is_always_lock_free, "atomic<bitset> isn't lock-free");
|
||||
#endif
|
||||
|
||||
ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
|
||||
{ assert(mEnabledEvts.is_lock_free()); }
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EffectSlot *ContextBase::getEffectSlot()
|
||||
{
|
||||
for(auto& cluster : mEffectSlotClusters)
|
||||
{
|
||||
for(size_t i{0};i < EffectSlotClusterSize;++i)
|
||||
{
|
||||
if(!cluster[i].InUse)
|
||||
return &cluster[i];
|
||||
}
|
||||
}
|
||||
|
||||
if(1 >= std::numeric_limits<int>::max()/EffectSlotClusterSize - mEffectSlotClusters.size())
|
||||
throw std::runtime_error{"Allocating too many effect slots"};
|
||||
const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize};
|
||||
TRACE("Increasing allocated effect slots to %zu\n", totalcount);
|
||||
|
||||
mEffectSlotClusters.emplace_back(std::make_unique<EffectSlot[]>(EffectSlotClusterSize));
|
||||
return getEffectSlot();
|
||||
}
|
||||
171
externals/openal-soft/core/context.h
vendored
Normal file
171
externals/openal-soft/core/context.h
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
#ifndef CORE_CONTEXT_H
|
||||
#define CORE_CONTEXT_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "async_event.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 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;
|
||||
using AsyncEventBitset = std::bitset<AsyncEvent::UserEventCount>;
|
||||
std::atomic<AsyncEventBitset> 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;
|
||||
|
||||
|
||||
static constexpr size_t EffectSlotClusterSize{4};
|
||||
EffectSlot *getEffectSlot();
|
||||
|
||||
using EffectSlotCluster = std::unique_ptr<EffectSlot[]>;
|
||||
al::vector<EffectSlotCluster> mEffectSlotClusters;
|
||||
|
||||
|
||||
ContextBase(DeviceBase *device);
|
||||
ContextBase(const ContextBase&) = delete;
|
||||
ContextBase& operator=(const ContextBase&) = delete;
|
||||
~ContextBase();
|
||||
};
|
||||
|
||||
#endif /* CORE_CONTEXT_H */
|
||||
346
externals/openal-soft/core/converter.cpp
vendored
Normal file
346
externals/openal-soft/core/converter.cpp
vendored
Normal file
@ -0,0 +1,346 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits.h>
|
||||
|
||||
#include "albit.h"
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "fpu_ctrl.h"
|
||||
|
||||
|
||||
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((chanmask&1)) LIKELY
|
||||
{
|
||||
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 SampleConverter::Create(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 = MaxResamplerPadding;
|
||||
converter->mFracOffset = 0;
|
||||
for(auto &chan : converter->mChan)
|
||||
{
|
||||
const al::span<float> buffer{chan.PrevSamples};
|
||||
std::fill(buffer.begin(), buffer.end(), 0.0f);
|
||||
}
|
||||
|
||||
/* 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 = [](const InterpState*, const float *RESTRICT src, uint, const uint,
|
||||
const al::span<float> dst) { std::copy_n(src, dst.size(), dst.begin()); };
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
uint SampleConverter::availableOut(uint srcframes) const
|
||||
{
|
||||
if(srcframes < 1)
|
||||
{
|
||||
/* No output samples if there's no input samples. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
if(prepcount < MaxResamplerPadding && MaxResamplerPadding - prepcount >= srcframes)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t DataSize64{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)
|
||||
{
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
const uint readable{minu(NumSrcSamples, BufferLineSize - prepcount)};
|
||||
|
||||
if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable)
|
||||
{
|
||||
/* 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, readable);
|
||||
|
||||
mSrcPrepCount = prepcount + readable;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
float *RESTRICT SrcData{mSrcSamples};
|
||||
float *RESTRICT DstData{mDstSamples};
|
||||
uint DataPosFrac{mFracOffset};
|
||||
uint64_t DataSize64{prepcount};
|
||||
DataSize64 += readable;
|
||||
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);
|
||||
|
||||
const uint DataPosEnd{DstSize*increment + DataPosFrac};
|
||||
const uint SrcDataEnd{DataPosEnd>>MixerFracBits};
|
||||
|
||||
assert(prepcount+readable >= SrcDataEnd);
|
||||
const uint nextprep{minu(prepcount + readable - SrcDataEnd, MaxResamplerPadding)};
|
||||
|
||||
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, readable);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
std::copy_n(SrcData+SrcDataEnd, nextprep, mChan[chan].PrevSamples);
|
||||
std::fill(std::begin(mChan[chan].PrevSamples)+nextprep,
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
mResample(&mState, SrcData+MaxResamplerEdge, DataPosFrac, increment,
|
||||
{DstData, DstSize});
|
||||
|
||||
StoreSamples(DstSamples, DstData, mChan.size(), mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
mSrcPrepCount = nextprep;
|
||||
mFracOffset = DataPosEnd & MixerFracMask;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
const uint srcread{minu(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)};
|
||||
SamplesIn += SrcFrameSize*srcread;
|
||||
NumSrcSamples -= srcread;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
66
externals/openal-soft/core/converter.h
vendored
Normal file
66
externals/openal-soft/core/converter.h
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef CORE_CONVERTER_H
|
||||
#define CORE_CONVERTER_H
|
||||
|
||||
#include <chrono>
|
||||
#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{};
|
||||
|
||||
uint 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;
|
||||
|
||||
using SampleOffset = std::chrono::duration<int64_t, std::ratio<1,MixerFracOne>>;
|
||||
SampleOffset currentInputDelay() const noexcept
|
||||
{
|
||||
const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge};
|
||||
return SampleOffset{(prep<<MixerFracBits) + mFracOffset};
|
||||
}
|
||||
|
||||
static std::unique_ptr<SampleConverter> Create(DevFmtType srcType, DevFmtType dstType,
|
||||
size_t numchans, uint srcRate, uint dstRate, Resampler resampler);
|
||||
|
||||
DEF_FAM_NEWDEL(SampleConverter, mChan)
|
||||
};
|
||||
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
|
||||
|
||||
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 */
|
||||
141
externals/openal-soft/core/cpu_caps.cpp
vendored
Normal file
141
externals/openal-soft/core/cpu_caps.cpp
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cpu_caps.h"
|
||||
|
||||
#if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64))
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#ifndef PF_ARM_NEON_INSTRUCTIONS_AVAILABLE
|
||||
#define PF_ARM_NEON_INSTRUCTIONS_AVAILABLE 19
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CPUID_H)
|
||||
#include <cpuid.h>
|
||||
#elif defined(HAVE_INTRIN_H)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
|
||||
int CPUCapFlags{0};
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(HAVE_GCC_GET_CPUID) \
|
||||
&& (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))
|
||||
using reg_type = unsigned int;
|
||||
inline std::array<reg_type,4> get_cpuid(unsigned int f)
|
||||
{
|
||||
std::array<reg_type,4> ret{};
|
||||
__get_cpuid(f, ret.data(), &ret[1], &ret[2], &ret[3]);
|
||||
return ret;
|
||||
}
|
||||
#define CAN_GET_CPUID
|
||||
#elif defined(HAVE_CPUID_INTRINSIC) \
|
||||
&& (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))
|
||||
using reg_type = int;
|
||||
inline std::array<reg_type,4> get_cpuid(unsigned int f)
|
||||
{
|
||||
std::array<reg_type,4> ret{};
|
||||
(__cpuid)(ret.data(), f);
|
||||
return ret;
|
||||
}
|
||||
#define CAN_GET_CPUID
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
al::optional<CPUInfo> GetCPUInfo()
|
||||
{
|
||||
CPUInfo ret;
|
||||
|
||||
#ifdef CAN_GET_CPUID
|
||||
auto cpuregs = get_cpuid(0);
|
||||
if(cpuregs[0] == 0)
|
||||
return al::nullopt;
|
||||
|
||||
const reg_type maxfunc{cpuregs[0]};
|
||||
|
||||
cpuregs = get_cpuid(0x80000000);
|
||||
const reg_type maxextfunc{cpuregs[0]};
|
||||
|
||||
ret.mVendor.append(reinterpret_cast<char*>(&cpuregs[1]), 4);
|
||||
ret.mVendor.append(reinterpret_cast<char*>(&cpuregs[3]), 4);
|
||||
ret.mVendor.append(reinterpret_cast<char*>(&cpuregs[2]), 4);
|
||||
auto iter_end = std::remove(ret.mVendor.begin(), ret.mVendor.end(), '\0');
|
||||
iter_end = std::unique(ret.mVendor.begin(), iter_end,
|
||||
[](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); });
|
||||
ret.mVendor.erase(iter_end, ret.mVendor.end());
|
||||
if(!ret.mVendor.empty() && std::isspace(ret.mVendor.back()))
|
||||
ret.mVendor.pop_back();
|
||||
if(!ret.mVendor.empty() && std::isspace(ret.mVendor.front()))
|
||||
ret.mVendor.erase(ret.mVendor.begin());
|
||||
|
||||
if(maxextfunc >= 0x80000004)
|
||||
{
|
||||
cpuregs = get_cpuid(0x80000002);
|
||||
ret.mName.append(reinterpret_cast<char*>(cpuregs.data()), 16);
|
||||
cpuregs = get_cpuid(0x80000003);
|
||||
ret.mName.append(reinterpret_cast<char*>(cpuregs.data()), 16);
|
||||
cpuregs = get_cpuid(0x80000004);
|
||||
ret.mName.append(reinterpret_cast<char*>(cpuregs.data()), 16);
|
||||
iter_end = std::remove(ret.mName.begin(), ret.mName.end(), '\0');
|
||||
iter_end = std::unique(ret.mName.begin(), iter_end,
|
||||
[](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); });
|
||||
ret.mName.erase(iter_end, ret.mName.end());
|
||||
if(!ret.mName.empty() && std::isspace(ret.mName.back()))
|
||||
ret.mName.pop_back();
|
||||
if(!ret.mName.empty() && std::isspace(ret.mName.front()))
|
||||
ret.mName.erase(ret.mName.begin());
|
||||
}
|
||||
|
||||
if(maxfunc >= 1)
|
||||
{
|
||||
cpuregs = get_cpuid(1);
|
||||
if((cpuregs[3]&(1<<25)))
|
||||
ret.mCaps |= CPU_CAP_SSE;
|
||||
if((ret.mCaps&CPU_CAP_SSE) && (cpuregs[3]&(1<<26)))
|
||||
ret.mCaps |= CPU_CAP_SSE2;
|
||||
if((ret.mCaps&CPU_CAP_SSE2) && (cpuregs[2]&(1<<0)))
|
||||
ret.mCaps |= CPU_CAP_SSE3;
|
||||
if((ret.mCaps&CPU_CAP_SSE3) && (cpuregs[2]&(1<<19)))
|
||||
ret.mCaps |= CPU_CAP_SSE4_1;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Assume support for whatever's supported if we can't check for it */
|
||||
#if defined(HAVE_SSE4_1)
|
||||
#warning "Assuming SSE 4.1 run-time support!"
|
||||
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
|
||||
#elif defined(HAVE_SSE3)
|
||||
#warning "Assuming SSE 3 run-time support!"
|
||||
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
|
||||
#elif defined(HAVE_SSE2)
|
||||
#warning "Assuming SSE 2 run-time support!"
|
||||
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2;
|
||||
#elif defined(HAVE_SSE)
|
||||
#warning "Assuming SSE run-time support!"
|
||||
ret.mCaps |= CPU_CAP_SSE;
|
||||
#endif
|
||||
#endif /* CAN_GET_CPUID */
|
||||
|
||||
#ifdef HAVE_NEON
|
||||
#ifdef __ARM_NEON
|
||||
ret.mCaps |= CPU_CAP_NEON;
|
||||
#elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64))
|
||||
if(IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
|
||||
ret.mCaps |= CPU_CAP_NEON;
|
||||
#else
|
||||
#warning "Assuming NEON run-time support!"
|
||||
ret.mCaps |= CPU_CAP_NEON;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
26
externals/openal-soft/core/cpu_caps.h
vendored
Normal file
26
externals/openal-soft/core/cpu_caps.h
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef CORE_CPU_CAPS_H
|
||||
#define CORE_CPU_CAPS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
|
||||
extern int CPUCapFlags;
|
||||
enum {
|
||||
CPU_CAP_SSE = 1<<0,
|
||||
CPU_CAP_SSE2 = 1<<1,
|
||||
CPU_CAP_SSE3 = 1<<2,
|
||||
CPU_CAP_SSE4_1 = 1<<3,
|
||||
CPU_CAP_NEON = 1<<4,
|
||||
};
|
||||
|
||||
struct CPUInfo {
|
||||
std::string mVendor;
|
||||
std::string mName;
|
||||
int mCaps{0};
|
||||
};
|
||||
|
||||
al::optional<CPUInfo> GetCPUInfo();
|
||||
|
||||
#endif /* CORE_CPU_CAPS_H */
|
||||
13
externals/openal-soft/core/cubic_defs.h
vendored
Normal file
13
externals/openal-soft/core/cubic_defs.h
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef CORE_CUBIC_DEFS_H
|
||||
#define CORE_CUBIC_DEFS_H
|
||||
|
||||
/* The number of distinct phase intervals within the cubic filter tables. */
|
||||
constexpr unsigned int CubicPhaseBits{5};
|
||||
constexpr unsigned int CubicPhaseCount{1 << CubicPhaseBits};
|
||||
|
||||
struct CubicCoefficients {
|
||||
float mCoeffs[4];
|
||||
float mDeltas[4];
|
||||
};
|
||||
|
||||
#endif /* CORE_CUBIC_DEFS_H */
|
||||
59
externals/openal-soft/core/cubic_tables.cpp
vendored
Normal file
59
externals/openal-soft/core/cubic_tables.cpp
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
#include "cubic_tables.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
struct SplineFilterArray {
|
||||
alignas(16) CubicCoefficients mTable[CubicPhaseCount]{};
|
||||
|
||||
constexpr SplineFilterArray()
|
||||
{
|
||||
/* Fill in the main coefficients. */
|
||||
for(size_t pi{0};pi < CubicPhaseCount;++pi)
|
||||
{
|
||||
const double mu{static_cast<double>(pi) / CubicPhaseCount};
|
||||
const double mu2{mu*mu}, mu3{mu2*mu};
|
||||
mTable[pi].mCoeffs[0] = static_cast<float>(-0.5*mu3 + mu2 + -0.5*mu);
|
||||
mTable[pi].mCoeffs[1] = static_cast<float>( 1.5*mu3 + -2.5*mu2 + 1.0);
|
||||
mTable[pi].mCoeffs[2] = static_cast<float>(-1.5*mu3 + 2.0*mu2 + 0.5*mu);
|
||||
mTable[pi].mCoeffs[3] = static_cast<float>( 0.5*mu3 + -0.5*mu2);
|
||||
}
|
||||
|
||||
/* Fill in the coefficient deltas. */
|
||||
for(size_t pi{0};pi < CubicPhaseCount-1;++pi)
|
||||
{
|
||||
mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
const size_t pi{CubicPhaseCount - 1};
|
||||
mTable[pi].mDeltas[0] = -mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = -mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = 1.0f - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = -mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
constexpr auto getTable() const noexcept { return al::as_span(mTable); }
|
||||
};
|
||||
|
||||
constexpr SplineFilterArray SplineFilter{};
|
||||
|
||||
} // namespace
|
||||
|
||||
const CubicTable gCubicSpline{SplineFilter.getTable()};
|
||||
17
externals/openal-soft/core/cubic_tables.h
vendored
Normal file
17
externals/openal-soft/core/cubic_tables.h
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef CORE_CUBIC_TABLES_H
|
||||
#define CORE_CUBIC_TABLES_H
|
||||
|
||||
#include "alspan.h"
|
||||
#include "cubic_defs.h"
|
||||
|
||||
|
||||
struct CubicTable {
|
||||
al::span<const CubicCoefficients,CubicPhaseCount> Tab;
|
||||
};
|
||||
|
||||
/* A Catmull-Rom spline. The spline passes through the center two samples,
|
||||
* ensuring no discontinuity while moving through a series of samples.
|
||||
*/
|
||||
extern const CubicTable gCubicSpline;
|
||||
|
||||
#endif /* CORE_CUBIC_TABLES_H */
|
||||
46
externals/openal-soft/core/dbus_wrap.cpp
vendored
Normal file
46
externals/openal-soft/core/dbus_wrap.cpp
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
void *dbus_handle{nullptr};
|
||||
#define DECL_FUNC(x) decltype(p##x) p##x{};
|
||||
DBUS_FUNCTIONS(DECL_FUNC)
|
||||
#undef DECL_FUNC
|
||||
|
||||
void PrepareDBus()
|
||||
{
|
||||
static constexpr char libname[] = "libdbus-1.so.3";
|
||||
|
||||
auto load_func = [](auto &f, const char *name) -> void
|
||||
{ f = reinterpret_cast<std::remove_reference_t<decltype(f)>>(GetSymbol(dbus_handle, name)); };
|
||||
#define LOAD_FUNC(x) do { \
|
||||
load_func(p##x, #x); \
|
||||
if(!p##x) \
|
||||
{ \
|
||||
WARN("Failed to load function %s\n", #x); \
|
||||
CloseLib(dbus_handle); \
|
||||
dbus_handle = nullptr; \
|
||||
return; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
dbus_handle = LoadLib(libname);
|
||||
if(!dbus_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", libname);
|
||||
return;
|
||||
}
|
||||
|
||||
DBUS_FUNCTIONS(LOAD_FUNC)
|
||||
#undef LOAD_FUNC
|
||||
}
|
||||
#endif
|
||||
87
externals/openal-soft/core/dbus_wrap.h
vendored
Normal file
87
externals/openal-soft/core/dbus_wrap.h
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef CORE_DBUS_WRAP_H
|
||||
#define CORE_DBUS_WRAP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#include "dynload.h"
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define DBUS_FUNCTIONS(MAGIC) \
|
||||
MAGIC(dbus_error_init) \
|
||||
MAGIC(dbus_error_free) \
|
||||
MAGIC(dbus_bus_get) \
|
||||
MAGIC(dbus_connection_set_exit_on_disconnect) \
|
||||
MAGIC(dbus_connection_unref) \
|
||||
MAGIC(dbus_connection_send_with_reply_and_block) \
|
||||
MAGIC(dbus_message_unref) \
|
||||
MAGIC(dbus_message_new_method_call) \
|
||||
MAGIC(dbus_message_append_args) \
|
||||
MAGIC(dbus_message_iter_init) \
|
||||
MAGIC(dbus_message_iter_next) \
|
||||
MAGIC(dbus_message_iter_recurse) \
|
||||
MAGIC(dbus_message_iter_get_arg_type) \
|
||||
MAGIC(dbus_message_iter_get_basic) \
|
||||
MAGIC(dbus_set_error_from_message)
|
||||
|
||||
extern void *dbus_handle;
|
||||
#define DECL_FUNC(x) extern decltype(x) *p##x;
|
||||
DBUS_FUNCTIONS(DECL_FUNC)
|
||||
#undef DECL_FUNC
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define dbus_error_init (*pdbus_error_init)
|
||||
#define dbus_error_free (*pdbus_error_free)
|
||||
#define dbus_bus_get (*pdbus_bus_get)
|
||||
#define dbus_connection_set_exit_on_disconnect (*pdbus_connection_set_exit_on_disconnect)
|
||||
#define dbus_connection_unref (*pdbus_connection_unref)
|
||||
#define dbus_connection_send_with_reply_and_block (*pdbus_connection_send_with_reply_and_block)
|
||||
#define dbus_message_unref (*pdbus_message_unref)
|
||||
#define dbus_message_new_method_call (*pdbus_message_new_method_call)
|
||||
#define dbus_message_append_args (*pdbus_message_append_args)
|
||||
#define dbus_message_iter_init (*pdbus_message_iter_init)
|
||||
#define dbus_message_iter_next (*pdbus_message_iter_next)
|
||||
#define dbus_message_iter_recurse (*pdbus_message_iter_recurse)
|
||||
#define dbus_message_iter_get_arg_type (*pdbus_message_iter_get_arg_type)
|
||||
#define dbus_message_iter_get_basic (*pdbus_message_iter_get_basic)
|
||||
#define dbus_set_error_from_message (*pdbus_set_error_from_message)
|
||||
#endif
|
||||
|
||||
void PrepareDBus();
|
||||
|
||||
inline auto HasDBus()
|
||||
{
|
||||
static std::once_flag init_dbus{};
|
||||
std::call_once(init_dbus, []{ PrepareDBus(); });
|
||||
return dbus_handle;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
constexpr bool HasDBus() noexcept { return true; }
|
||||
#endif /* HAVE_DYNLOAD */
|
||||
|
||||
|
||||
namespace dbus {
|
||||
|
||||
struct Error {
|
||||
Error() { dbus_error_init(&mError); }
|
||||
~Error() { dbus_error_free(&mError); }
|
||||
DBusError* operator->() { return &mError; }
|
||||
DBusError &get() { return mError; }
|
||||
private:
|
||||
DBusError mError{};
|
||||
};
|
||||
|
||||
struct ConnectionDeleter {
|
||||
void operator()(DBusConnection *c) { dbus_connection_unref(c); }
|
||||
};
|
||||
using ConnectionPtr = std::unique_ptr<DBusConnection,ConnectionDeleter>;
|
||||
|
||||
} // namespace dbus
|
||||
|
||||
#endif /* CORE_DBUS_WRAP_H */
|
||||
67
externals/openal-soft/core/devformat.cpp
vendored
Normal file
67
externals/openal-soft/core/devformat.cpp
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "devformat.h"
|
||||
|
||||
|
||||
uint BytesFromDevFmt(DevFmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevFmtByte: return sizeof(int8_t);
|
||||
case DevFmtUByte: return sizeof(uint8_t);
|
||||
case DevFmtShort: return sizeof(int16_t);
|
||||
case DevFmtUShort: return sizeof(uint16_t);
|
||||
case DevFmtInt: return sizeof(int32_t);
|
||||
case DevFmtUInt: return sizeof(uint32_t);
|
||||
case DevFmtFloat: return sizeof(float);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case DevFmtMono: return 1;
|
||||
case DevFmtStereo: return 2;
|
||||
case DevFmtQuad: return 4;
|
||||
case DevFmtX51: return 6;
|
||||
case DevFmtX61: return 7;
|
||||
case DevFmtX71: return 8;
|
||||
case DevFmtX714: return 12;
|
||||
case DevFmtX3D71: return 8;
|
||||
case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *DevFmtTypeString(DevFmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevFmtByte: return "Int8";
|
||||
case DevFmtUByte: return "UInt8";
|
||||
case DevFmtShort: return "Int16";
|
||||
case DevFmtUShort: return "UInt16";
|
||||
case DevFmtInt: return "Int32";
|
||||
case DevFmtUInt: return "UInt32";
|
||||
case DevFmtFloat: return "Float32";
|
||||
}
|
||||
return "(unknown type)";
|
||||
}
|
||||
const char *DevFmtChannelsString(DevFmtChannels chans) noexcept
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case DevFmtMono: return "Mono";
|
||||
case DevFmtStereo: return "Stereo";
|
||||
case DevFmtQuad: return "Quadraphonic";
|
||||
case DevFmtX51: return "5.1 Surround";
|
||||
case DevFmtX61: return "6.1 Surround";
|
||||
case DevFmtX71: return "7.1 Surround";
|
||||
case DevFmtX714: return "7.1.4 Surround";
|
||||
case DevFmtX3D71: return "3D7.1 Surround";
|
||||
case DevFmtAmbi3D: return "Ambisonic 3D";
|
||||
}
|
||||
return "(unknown channels)";
|
||||
}
|
||||
122
externals/openal-soft/core/devformat.h
vendored
Normal file
122
externals/openal-soft/core/devformat.h
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
#ifndef CORE_DEVFORMAT_H
|
||||
#define CORE_DEVFORMAT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
enum Channel : unsigned char {
|
||||
FrontLeft = 0,
|
||||
FrontRight,
|
||||
FrontCenter,
|
||||
LFE,
|
||||
BackLeft,
|
||||
BackRight,
|
||||
BackCenter,
|
||||
SideLeft,
|
||||
SideRight,
|
||||
|
||||
TopCenter,
|
||||
TopFrontLeft,
|
||||
TopFrontCenter,
|
||||
TopFrontRight,
|
||||
TopBackLeft,
|
||||
TopBackCenter,
|
||||
TopBackRight,
|
||||
|
||||
Aux0,
|
||||
Aux1,
|
||||
Aux2,
|
||||
Aux3,
|
||||
Aux4,
|
||||
Aux5,
|
||||
Aux6,
|
||||
Aux7,
|
||||
Aux8,
|
||||
Aux9,
|
||||
Aux10,
|
||||
Aux11,
|
||||
Aux12,
|
||||
Aux13,
|
||||
Aux14,
|
||||
Aux15,
|
||||
|
||||
MaxChannels
|
||||
};
|
||||
|
||||
|
||||
/* Device formats */
|
||||
enum DevFmtType : unsigned char {
|
||||
DevFmtByte,
|
||||
DevFmtUByte,
|
||||
DevFmtShort,
|
||||
DevFmtUShort,
|
||||
DevFmtInt,
|
||||
DevFmtUInt,
|
||||
DevFmtFloat,
|
||||
|
||||
DevFmtTypeDefault = DevFmtFloat
|
||||
};
|
||||
enum DevFmtChannels : unsigned char {
|
||||
DevFmtMono,
|
||||
DevFmtStereo,
|
||||
DevFmtQuad,
|
||||
DevFmtX51,
|
||||
DevFmtX61,
|
||||
DevFmtX71,
|
||||
DevFmtX714,
|
||||
DevFmtX3D71,
|
||||
DevFmtAmbi3D,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
};
|
||||
#define MAX_OUTPUT_CHANNELS 16
|
||||
|
||||
/* DevFmtType traits, providing the type, etc given a DevFmtType. */
|
||||
template<DevFmtType T>
|
||||
struct DevFmtTypeTraits { };
|
||||
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtByte> { using Type = int8_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUByte> { using Type = uint8_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtShort> { using Type = int16_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUShort> { using Type = uint16_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtInt> { using Type = int32_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtUInt> { using Type = uint32_t; };
|
||||
template<>
|
||||
struct DevFmtTypeTraits<DevFmtFloat> { using Type = float; };
|
||||
|
||||
template<DevFmtType T>
|
||||
using DevFmtType_t = typename DevFmtTypeTraits<T>::Type;
|
||||
|
||||
|
||||
uint BytesFromDevFmt(DevFmtType type) noexcept;
|
||||
uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept;
|
||||
inline uint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, uint ambiorder) noexcept
|
||||
{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); }
|
||||
|
||||
const char *DevFmtTypeString(DevFmtType type) noexcept;
|
||||
const char *DevFmtChannelsString(DevFmtChannels chans) noexcept;
|
||||
|
||||
enum class DevAmbiLayout : bool {
|
||||
FuMa,
|
||||
ACN,
|
||||
|
||||
Default = ACN
|
||||
};
|
||||
|
||||
enum class DevAmbiScaling : unsigned char {
|
||||
FuMa,
|
||||
SN3D,
|
||||
N3D,
|
||||
|
||||
Default = SN3D
|
||||
};
|
||||
|
||||
#endif /* CORE_DEVFORMAT_H */
|
||||
23
externals/openal-soft/core/device.cpp
vendored
Normal file
23
externals/openal-soft/core/device.cpp
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
#include "bs2b.h"
|
||||
#include "device.h"
|
||||
#include "front_stablizer.h"
|
||||
#include "hrtf.h"
|
||||
#include "mastering.h"
|
||||
|
||||
|
||||
al::FlexArray<ContextBase*> DeviceBase::sEmptyContextArray{0u};
|
||||
|
||||
|
||||
DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray}
|
||||
{
|
||||
}
|
||||
|
||||
DeviceBase::~DeviceBase()
|
||||
{
|
||||
auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed);
|
||||
if(oldarray != &sEmptyContextArray) delete oldarray;
|
||||
}
|
||||
345
externals/openal-soft/core/device.h
vendored
Normal file
345
externals/openal-soft/core/device.h
vendored
Normal file
@ -0,0 +1,345 @@
|
||||
#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 48000
|
||||
|
||||
#define DEFAULT_UPDATE_SIZE 960 /* 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;
|
||||
al::span<const TargetMix> targets;
|
||||
};
|
||||
|
||||
|
||||
struct DistanceComp {
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
static constexpr uint MaxDelay{1024};
|
||||
|
||||
struct ChanData {
|
||||
float Gain{1.0f};
|
||||
uint Length{0u}; /* Valid range is [0...MaxDelay). */
|
||||
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)
|
||||
};
|
||||
|
||||
|
||||
constexpr uint InvalidChannelIndex{~0u};
|
||||
|
||||
struct BFChannelConfig {
|
||||
float Scale;
|
||||
uint Index;
|
||||
};
|
||||
|
||||
struct MixParams {
|
||||
/* Coefficient channel mapping for mixing to the buffer. */
|
||||
std::array<BFChannelConfig,MaxAmbiChannels> AmbiMap{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
|
||||
/**
|
||||
* Helper to set an identity/pass-through panning for ambisonic mixing. The
|
||||
* source is expected to be a 3D ACN/N3D ambisonic buffer, and for each
|
||||
* channel [0...count), the given functor is called with the source channel
|
||||
* index, destination channel index, and the gain for that channel. If the
|
||||
* destination channel is INVALID_CHANNEL_INDEX, the given source channel
|
||||
* is not used for output.
|
||||
*/
|
||||
template<typename F>
|
||||
void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const
|
||||
{
|
||||
const size_t numIn{inmix.Buffer.size()};
|
||||
const size_t numOut{Buffer.size()};
|
||||
for(size_t i{0};i < numIn;++i)
|
||||
{
|
||||
auto idx = InvalidChannelIndex;
|
||||
auto gain = 0.0f;
|
||||
|
||||
for(size_t j{0};j < numOut;++j)
|
||||
{
|
||||
if(AmbiMap[j].Index == inmix.AmbiMap[i].Index)
|
||||
{
|
||||
idx = static_cast<uint>(j);
|
||||
gain = AmbiMap[j].Scale * gainbase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
func(i, idx, gain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RealMixParams {
|
||||
al::span<const InputRemixMap> RemixMap;
|
||||
std::array<uint,MaxChannels> ChannelIndex{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
using AmbiRotateMatrix = std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels>;
|
||||
|
||||
enum {
|
||||
// Frequency was requested by the app or config file
|
||||
FrequencyRequest,
|
||||
// Channel configuration was requested by the app or 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};
|
||||
/* If the main device mix is horizontal/2D only. */
|
||||
bool m2DMixing{false};
|
||||
/* 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};
|
||||
|
||||
AmbiRotateMatrix mAmbiRotateMatrix{};
|
||||
AmbiRotateMatrix mAmbiRotateMatrix2{};
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
|
||||
static constexpr size_t MixerChannelsMax{16};
|
||||
using MixerBufferLine = std::array<float,MixerLineSize>;
|
||||
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
|
||||
alignas(16) std::array<float,MixerLineSize+MaxResamplerPadding> mResampleData;
|
||||
|
||||
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<UhjEncoderBase> 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(PostProcess) LIKELY (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, ...);
|
||||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
uint channelIdxByName(Channel chan) const noexcept
|
||||
{ return RealOut.ChannelIndex[chan]; }
|
||||
|
||||
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"
|
||||
|
||||
#endif /* CORE_DEVICE_H */
|
||||
197
externals/openal-soft/core/effects/base.h
vendored
Normal file
197
externals/openal-soft/core/effects/base.h
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
#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 {
|
||||
float Density;
|
||||
float Diffusion;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float GainLF;
|
||||
float DecayTime;
|
||||
float DecayHFRatio;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsGain;
|
||||
float ReflectionsDelay;
|
||||
float ReflectionsPan[3];
|
||||
float LateReverbGain;
|
||||
float LateReverbDelay;
|
||||
float LateReverbPan[3];
|
||||
float EchoTime;
|
||||
float EchoDepth;
|
||||
float ModulationTime;
|
||||
float ModulationDepth;
|
||||
float AirAbsorptionGainHF;
|
||||
float HFReference;
|
||||
float LFReference;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
} 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> {
|
||||
al::span<FloatBufferLine> mOutTarget;
|
||||
|
||||
|
||||
virtual ~EffectState() = default;
|
||||
|
||||
virtual void deviceUpdate(const DeviceBase *device, const BufferStorage *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 */
|
||||
19
externals/openal-soft/core/effectslot.cpp
vendored
Normal file
19
externals/openal-soft/core/effectslot.cpp
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
#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);
|
||||
}
|
||||
89
externals/openal-soft/core/effectslot.h
vendored
Normal file
89
externals/openal-soft/core/effectslot.h
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
#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 {
|
||||
bool InUse{false};
|
||||
|
||||
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{};
|
||||
al::intrusive_ptr<EffectState> mEffectState;
|
||||
|
||||
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. */
|
||||
al::vector<FloatBufferLine,16> mWetBuffer;
|
||||
|
||||
|
||||
static EffectSlotArray *CreatePtrArray(size_t count) noexcept;
|
||||
|
||||
DEF_NEWDEL(EffectSlot)
|
||||
};
|
||||
|
||||
#endif /* CORE_EFFECTSLOT_H */
|
||||
30
externals/openal-soft/core/except.cpp
vendored
Normal file
30
externals/openal-soft/core/except.cpp
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "except.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
base_exception::~base_exception() = default;
|
||||
|
||||
void base_exception::setMessage(const char* msg, std::va_list args)
|
||||
{
|
||||
std::va_list args2;
|
||||
va_copy(args2, args);
|
||||
int msglen{std::vsnprintf(nullptr, 0, msg, args)};
|
||||
if(msglen > 0) LIKELY
|
||||
{
|
||||
mMessage.resize(static_cast<size_t>(msglen)+1);
|
||||
std::vsnprintf(const_cast<char*>(mMessage.data()), mMessage.length(), msg, args2);
|
||||
mMessage.pop_back();
|
||||
}
|
||||
va_end(args2);
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
31
externals/openal-soft/core/except.h
vendored
Normal file
31
externals/openal-soft/core/except.h
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef CORE_EXCEPT_H
|
||||
#define CORE_EXCEPT_H
|
||||
|
||||
#include <cstdarg>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
class base_exception : public std::exception {
|
||||
std::string mMessage;
|
||||
|
||||
protected:
|
||||
base_exception() = default;
|
||||
virtual ~base_exception();
|
||||
|
||||
void setMessage(const char *msg, std::va_list args);
|
||||
|
||||
public:
|
||||
const char *what() const noexcept override { return mMessage.c_str(); }
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#define START_API_FUNC try
|
||||
|
||||
#define END_API_FUNC catch(...) { std::terminate(); }
|
||||
|
||||
#endif /* CORE_EXCEPT_H */
|
||||
168
externals/openal-soft/core/filters/biquad.cpp
vendored
Normal file
168
externals/openal-soft/core/filters/biquad.cpp
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "biquad.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ)
|
||||
{
|
||||
/* HACK: Limit gain to -100dB. This shouldn't ever happen, all callers
|
||||
* already clamp to minimum of 0.001, or have a limited range of values
|
||||
* that don't go below 0.126. But it seems to with some callers. This needs
|
||||
* to be investigated.
|
||||
*/
|
||||
gain = std::max(gain, Real(0.00001));
|
||||
|
||||
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};
|
||||
|
||||
Real sqrtgain_alpha_2;
|
||||
Real a[3]{ 1.0f, 0.0f, 0.0f };
|
||||
Real b[3]{ 1.0f, 0.0f, 0.0f };
|
||||
|
||||
/* Calculate filter coefficients depending on filter type */
|
||||
switch(type)
|
||||
{
|
||||
case BiquadType::HighShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
|
||||
a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::LowShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
|
||||
a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::Peaking:
|
||||
b[0] = 1.0f + alpha * gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha * gain;
|
||||
a[0] = 1.0f + alpha / gain;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha / gain;
|
||||
break;
|
||||
|
||||
case BiquadType::LowPass:
|
||||
b[0] = (1.0f - cos_w0) / 2.0f;
|
||||
b[1] = 1.0f - cos_w0;
|
||||
b[2] = (1.0f - cos_w0) / 2.0f;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::HighPass:
|
||||
b[0] = (1.0f + cos_w0) / 2.0f;
|
||||
b[1] = -(1.0f + cos_w0);
|
||||
b[2] = (1.0f + cos_w0) / 2.0f;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::BandPass:
|
||||
b[0] = alpha;
|
||||
b[1] = 0.0f;
|
||||
b[2] = -alpha;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
}
|
||||
|
||||
mA1 = a[1] / a[0];
|
||||
mA2 = a[2] / a[0];
|
||||
mB0 = b[0] / a[0];
|
||||
mB1 = b[1] / a[0];
|
||||
mB2 = b[2] / a[0];
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
||||
{
|
||||
const Real b0{mB0};
|
||||
const Real b1{mB1};
|
||||
const Real b2{mB2};
|
||||
const Real a1{mA1};
|
||||
const Real a2{mA2};
|
||||
Real z1{mZ1};
|
||||
Real z2{mZ2};
|
||||
|
||||
/* Processing loop is Transposed Direct Form II. This requires less storage
|
||||
* compared to Direct Form I (only two delay components, instead of a four-
|
||||
* sample history; the last two inputs and outputs), and works better for
|
||||
* floating-point which favors summing similarly-sized values while being
|
||||
* less bothered by overflow.
|
||||
*
|
||||
* See: http://www.earlevel.com/main/2003/02/28/biquads/
|
||||
*/
|
||||
auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real
|
||||
{
|
||||
const Real output{input*b0 + z1};
|
||||
z1 = input*b1 - output*a1 + z2;
|
||||
z2 = input*b2 - output*a2;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
|
||||
mZ1 = z1;
|
||||
mZ2 = z2;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::dualProcess(BiquadFilterR &other, const al::span<const Real> src,
|
||||
Real *dst)
|
||||
{
|
||||
const Real b00{mB0};
|
||||
const Real b01{mB1};
|
||||
const Real b02{mB2};
|
||||
const Real a01{mA1};
|
||||
const Real a02{mA2};
|
||||
const Real b10{other.mB0};
|
||||
const Real b11{other.mB1};
|
||||
const Real b12{other.mB2};
|
||||
const Real a11{other.mA1};
|
||||
const Real a12{other.mA2};
|
||||
Real z01{mZ1};
|
||||
Real z02{mZ2};
|
||||
Real z11{other.mZ1};
|
||||
Real z12{other.mZ2};
|
||||
|
||||
auto proc_sample = [b00,b01,b02,a01,a02,b10,b11,b12,a11,a12,&z01,&z02,&z11,&z12](Real input) noexcept -> Real
|
||||
{
|
||||
const Real tmpout{input*b00 + z01};
|
||||
z01 = input*b01 - tmpout*a01 + z02;
|
||||
z02 = input*b02 - tmpout*a02;
|
||||
input = tmpout;
|
||||
|
||||
const Real output{input*b10 + z11};
|
||||
z11 = input*b11 - output*a11 + z12;
|
||||
z12 = input*b12 - output*a12;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
|
||||
mZ1 = z01;
|
||||
mZ2 = z02;
|
||||
other.mZ1 = z11;
|
||||
other.mZ2 = z12;
|
||||
}
|
||||
|
||||
template class BiquadFilterR<float>;
|
||||
template class BiquadFilterR<double>;
|
||||
144
externals/openal-soft/core/filters/biquad.h
vendored
Normal file
144
externals/openal-soft/core/filters/biquad.h
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
#ifndef CORE_FILTERS_BIQUAD_H
|
||||
#define CORE_FILTERS_BIQUAD_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
/* Filters implementation is based on the "Cookbook formulae for audio
|
||||
* EQ biquad filter coefficients" by Robert Bristow-Johnson
|
||||
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
*/
|
||||
/* Implementation note: For the shelf and peaking filters, the specified gain
|
||||
* is for the centerpoint of the transition band. This better fits EFX filter
|
||||
* behavior, which expects the shelf's reference frequency to reach the given
|
||||
* gain. To set the gain for the shelf or peak itself, use the square root of
|
||||
* the desired linear gain (or halve the dB gain).
|
||||
*/
|
||||
|
||||
enum class BiquadType {
|
||||
/** EFX-style low-pass filter, specifying a gain and reference frequency. */
|
||||
HighShelf,
|
||||
/** EFX-style high-pass filter, specifying a gain and reference frequency. */
|
||||
LowShelf,
|
||||
/** Peaking filter, specifying a gain and reference frequency. */
|
||||
Peaking,
|
||||
|
||||
/** Low-pass cut-off filter, specifying a cut-off frequency. */
|
||||
LowPass,
|
||||
/** High-pass cut-off filter, specifying a cut-off frequency. */
|
||||
HighPass,
|
||||
/** Band-pass filter, specifying a center frequency. */
|
||||
BandPass,
|
||||
};
|
||||
|
||||
template<typename Real>
|
||||
class BiquadFilterR {
|
||||
/* Last two delayed components for direct form II. */
|
||||
Real mZ1{0}, mZ2{0};
|
||||
/* Transfer function coefficients "b" (numerator) */
|
||||
Real mB0{1}, mB1{0}, mB2{0};
|
||||
/* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
|
||||
Real mA1{0}, mA2{0};
|
||||
|
||||
void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ);
|
||||
|
||||
/**
|
||||
* Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using
|
||||
* the reference gain and shelf slope parameter.
|
||||
* \param gain 0 < gain
|
||||
* \param slope 0 < slope <= 1
|
||||
*/
|
||||
static Real rcpQFromSlope(Real gain, Real slope)
|
||||
{ return std::sqrt((gain + Real{1}/gain)*(Real{1}/slope - Real{1}) + Real{2}); }
|
||||
|
||||
/**
|
||||
* Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the
|
||||
* normalized reference frequency and bandwidth.
|
||||
* \param f0norm 0 < f0norm < 0.5.
|
||||
* \param bandwidth 0 < bandwidth
|
||||
*/
|
||||
static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
|
||||
{
|
||||
const Real w0{al::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 = Real{0}; }
|
||||
|
||||
/**
|
||||
* Sets the filter state for the specified filter type and its parameters.
|
||||
*
|
||||
* \param type The type of filter to apply.
|
||||
* \param f0norm The normalized reference frequency (ref / sample_rate).
|
||||
* This is the center point for the Shelf, Peaking, and BandPass filter
|
||||
* types, or the cutoff frequency for the LowPass and HighPass filter
|
||||
* types.
|
||||
* \param gain The gain for the reference frequency response. Only used by
|
||||
* the Shelf and Peaking filter types.
|
||||
* \param slope Slope steepness of the transition band.
|
||||
*/
|
||||
void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope)
|
||||
{
|
||||
gain = std::max<Real>(gain, 0.001f); /* Limit -60dB */
|
||||
setParams(type, f0norm, gain, rcpQFromSlope(gain, slope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter state for the specified filter type and its parameters.
|
||||
*
|
||||
* \param type The type of filter to apply.
|
||||
* \param f0norm The normalized reference frequency (ref / sample_rate).
|
||||
* This is the center point for the Shelf, Peaking, and BandPass filter
|
||||
* types, or the cutoff frequency for the LowPass and HighPass filter
|
||||
* types.
|
||||
* \param gain The gain for the reference frequency response. Only used by
|
||||
* the Shelf and Peaking filter types.
|
||||
* \param bandwidth Normalized bandwidth of the transition band.
|
||||
*/
|
||||
void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth)
|
||||
{ setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); }
|
||||
|
||||
void copyParamsFrom(const BiquadFilterR &other)
|
||||
{
|
||||
mB0 = other.mB0;
|
||||
mB1 = other.mB1;
|
||||
mB2 = other.mB2;
|
||||
mA1 = other.mA1;
|
||||
mA2 = other.mA2;
|
||||
}
|
||||
|
||||
void process(const al::span<const Real> src, Real *dst);
|
||||
/** Processes this filter and the other at the same time. */
|
||||
void dualProcess(BiquadFilterR &other, const al::span<const Real> src, Real *dst);
|
||||
|
||||
/* Rather hacky. It's just here to support "manual" processing. */
|
||||
std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
|
||||
void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
|
||||
Real processOne(const Real in, Real &z1, Real &z2) const noexcept
|
||||
{
|
||||
const Real out{in*mB0 + z1};
|
||||
z1 = in*mB1 - out*mA1 + z2;
|
||||
z2 = in*mB2 - out*mA2;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Real>
|
||||
struct DualBiquadR {
|
||||
BiquadFilterR<Real> &f0, &f1;
|
||||
|
||||
void process(const al::span<const Real> src, Real *dst)
|
||||
{ f0.dualProcess(f1, src, dst); }
|
||||
};
|
||||
|
||||
using BiquadFilter = BiquadFilterR<float>;
|
||||
using DualBiquad = DualBiquadR<float>;
|
||||
|
||||
#endif /* CORE_FILTERS_BIQUAD_H */
|
||||
367
externals/openal-soft/core/filters/nfc.cpp
vendored
Normal file
367
externals/openal-soft/core/filters/nfc.cpp
vendored
Normal file
@ -0,0 +1,367 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "nfc.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
/* Near-field control filters are the basis for handling the near-field effect.
|
||||
* The near-field effect is a bass-boost present in the directional components
|
||||
* of a recorded signal, created as a result of the wavefront curvature (itself
|
||||
* a function of sound distance). Proper reproduction dictates this be
|
||||
* compensated for using a bass-cut given the playback speaker distance, to
|
||||
* avoid excessive bass in the playback.
|
||||
*
|
||||
* For real-time rendered audio, emulating the near-field effect based on the
|
||||
* sound source's distance, and subsequently compensating for it at output
|
||||
* based on the speaker distances, can create a more realistic perception of
|
||||
* sound distance beyond a simple 1/r attenuation.
|
||||
*
|
||||
* These filters do just that. Each one applies a low-shelf filter, created as
|
||||
* the combination of a bass-boost for a given sound source distance (near-
|
||||
* field emulation) along with a bass-cut for a given control/speaker distance
|
||||
* (near-field compensation).
|
||||
*
|
||||
* Note that it is necessary to apply a cut along with the boost, since the
|
||||
* boost alone is unstable in higher-order ambisonics as it causes an infinite
|
||||
* DC gain (even first-order ambisonics requires there to be no DC offset for
|
||||
* the boost to work). Consequently, ambisonics requires a control parameter to
|
||||
* be used to avoid an unstable boost-only filter. NFC-HOA defines this control
|
||||
* as a reference delay, calculated with:
|
||||
*
|
||||
* reference_delay = control_distance / speed_of_sound
|
||||
*
|
||||
* This means w0 (for input) or w1 (for output) should be set to:
|
||||
*
|
||||
* wN = 1 / (reference_delay * sample_rate)
|
||||
*
|
||||
* when dealing with NFC-HOA content. For FOA input content, which does not
|
||||
* specify a reference_delay variable, w0 should be set to 0 to apply only
|
||||
* near-field compensation for output. It's important that w1 be a finite,
|
||||
* positive, non-0 value or else the bass-boost will become unstable again.
|
||||
* Also, w0 should not be too large compared to w1, to avoid excessively loud
|
||||
* low frequencies.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float B[5][4] = {
|
||||
{ 0.0f },
|
||||
{ 1.0f },
|
||||
{ 3.0f, 3.0f },
|
||||
{ 3.6778f, 6.4595f, 2.3222f },
|
||||
{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }
|
||||
};
|
||||
|
||||
NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter1 nfc{};
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
/* 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 = nfc.base_gain * g_0;
|
||||
nfc.b1 = 2.0f * b_00 / g_0;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_00{B[1][0] * r};
|
||||
const float g_0{1.0f + b_00};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_0;
|
||||
nfc->b1 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter2 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float r;
|
||||
|
||||
/* 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;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc.gain = nfc.base_gain * g_1;
|
||||
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc.b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[2][0] * r};
|
||||
const float b_11{B[2][1] * r * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter3 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
/* 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;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
b_00 = B[3][2] * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc.gain = 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;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[3][0] * r};
|
||||
const float b_11{B[3][1] * r * r};
|
||||
const float b_00{B[3][2] * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00};
|
||||
|
||||
nfc->gain = nfc->base_gain * (g_1 * g_0);
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
|
||||
{
|
||||
NfcFilter4 nfc{};
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, b_01, g_0;
|
||||
float r;
|
||||
|
||||
/* 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;
|
||||
b_10 = B[4][0] * r;
|
||||
b_11 = B[4][1] * r * r;
|
||||
b_00 = B[4][2] * r;
|
||||
b_01 = B[4][3] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
g_0 = 1.0f + b_00 + b_01;
|
||||
|
||||
nfc.gain = 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;
|
||||
|
||||
return nfc;
|
||||
}
|
||||
|
||||
void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
|
||||
{
|
||||
const float r{0.5f * w0};
|
||||
const float b_10{B[4][0] * r};
|
||||
const float b_11{B[4][1] * r * r};
|
||||
const float b_00{B[4][2] * r};
|
||||
const float b_01{B[4][3] * r * r};
|
||||
const float g_1{1.0f + b_10 + b_11};
|
||||
const float g_0{1.0f + b_00 + b_01};
|
||||
|
||||
nfc->gain = nfc->base_gain * (g_1 * g_0);
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
|
||||
nfc->b4 = 4.0f * b_01 / g_0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void NfcFilter::init(const float w1) noexcept
|
||||
{
|
||||
first = NfcFilterCreate1(0.0f, w1);
|
||||
second = NfcFilterCreate2(0.0f, w1);
|
||||
third = NfcFilterCreate3(0.0f, w1);
|
||||
fourth = NfcFilterCreate4(0.0f, w1);
|
||||
}
|
||||
|
||||
void NfcFilter::adjust(const float w0) noexcept
|
||||
{
|
||||
NfcFilterAdjust1(&first, w0);
|
||||
NfcFilterAdjust2(&second, w0);
|
||||
NfcFilterAdjust3(&third, w0);
|
||||
NfcFilterAdjust4(&fourth, w0);
|
||||
}
|
||||
|
||||
|
||||
void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{first.gain};
|
||||
const float b1{first.b1};
|
||||
const float a1{first.a1};
|
||||
float z1{first.z[0]};
|
||||
auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
|
||||
{
|
||||
const float y{in*gain - a1*z1};
|
||||
const float out{y + b1*z1};
|
||||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
first.z[0] = z1;
|
||||
}
|
||||
|
||||
void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{second.gain};
|
||||
const float b1{second.b1};
|
||||
const float b2{second.b2};
|
||||
const float a1{second.a1};
|
||||
const float a2{second.a2};
|
||||
float z1{second.z[0]};
|
||||
float z2{second.z[1]};
|
||||
auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
|
||||
{
|
||||
const float y{in*gain - a1*z1 - a2*z2};
|
||||
const float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
second.z[0] = z1;
|
||||
second.z[1] = z2;
|
||||
}
|
||||
|
||||
void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{third.gain};
|
||||
const float b1{third.b1};
|
||||
const float b2{third.b2};
|
||||
const float b3{third.b3};
|
||||
const float a1{third.a1};
|
||||
const float a2{third.a2};
|
||||
const float a3{third.a3};
|
||||
float z1{third.z[0]};
|
||||
float z2{third.z[1]};
|
||||
float z3{third.z[2]};
|
||||
auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
|
||||
{
|
||||
float y{in*gain - a1*z1 - a2*z2};
|
||||
float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
y = out - a3*z3;
|
||||
out = y + b3*z3;
|
||||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
third.z[0] = z1;
|
||||
third.z[1] = z2;
|
||||
third.z[2] = z3;
|
||||
}
|
||||
|
||||
void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
|
||||
{
|
||||
const float gain{fourth.gain};
|
||||
const float b1{fourth.b1};
|
||||
const float b2{fourth.b2};
|
||||
const float b3{fourth.b3};
|
||||
const float b4{fourth.b4};
|
||||
const float a1{fourth.a1};
|
||||
const float a2{fourth.a2};
|
||||
const float a3{fourth.a3};
|
||||
const float a4{fourth.a4};
|
||||
float z1{fourth.z[0]};
|
||||
float z2{fourth.z[1]};
|
||||
float z3{fourth.z[2]};
|
||||
float z4{fourth.z[3]};
|
||||
auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
|
||||
{
|
||||
float y{in*gain - a1*z1 - a2*z2};
|
||||
float out{y + b1*z1 + b2*z2};
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
y = out - a3*z3 - a4*z4;
|
||||
out = y + b3*z3 + b4*z4;
|
||||
z4 += z3;
|
||||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
fourth.z[0] = z1;
|
||||
fourth.z[1] = z2;
|
||||
fourth.z[2] = z3;
|
||||
fourth.z[3] = z4;
|
||||
}
|
||||
63
externals/openal-soft/core/filters/nfc.h
vendored
Normal file
63
externals/openal-soft/core/filters/nfc.h
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef CORE_FILTERS_NFC_H
|
||||
#define CORE_FILTERS_NFC_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
struct NfcFilter1 {
|
||||
float base_gain, gain;
|
||||
float b1, a1;
|
||||
float z[1];
|
||||
};
|
||||
struct NfcFilter2 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, a1, a2;
|
||||
float z[2];
|
||||
};
|
||||
struct NfcFilter3 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, a1, a2, a3;
|
||||
float z[3];
|
||||
};
|
||||
struct NfcFilter4 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, b4, a1, a2, a3, a4;
|
||||
float z[4];
|
||||
};
|
||||
|
||||
class NfcFilter {
|
||||
NfcFilter1 first;
|
||||
NfcFilter2 second;
|
||||
NfcFilter3 third;
|
||||
NfcFilter4 fourth;
|
||||
|
||||
public:
|
||||
/* NOTE:
|
||||
* w0 = speed_of_sound / (source_distance * sample_rate);
|
||||
* w1 = speed_of_sound / (control_distance * sample_rate);
|
||||
*
|
||||
* Generally speaking, the control distance should be approximately the
|
||||
* average speaker distance, or based on the reference delay if outputing
|
||||
* NFC-HOA. It must not be negative, 0, or infinite. The source distance
|
||||
* should not be too small relative to the control distance.
|
||||
*/
|
||||
|
||||
void init(const float w1) noexcept;
|
||||
void adjust(const float w0) noexcept;
|
||||
|
||||
/* Near-field control filter for first-order ambisonic channels (1-3). */
|
||||
void process1(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for second-order ambisonic channels (4-8). */
|
||||
void process2(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for third-order ambisonic channels (9-15). */
|
||||
void process3(const al::span<const float> src, float *RESTRICT dst);
|
||||
|
||||
/* Near-field control filter for fourth-order ambisonic channels (16-24). */
|
||||
void process4(const al::span<const float> src, float *RESTRICT dst);
|
||||
};
|
||||
|
||||
#endif /* CORE_FILTERS_NFC_H */
|
||||
179
externals/openal-soft/core/filters/splitter.cpp
vendored
Normal file
179
externals/openal-soft/core/filters/splitter.cpp
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "splitter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::init(Real f0norm)
|
||||
{
|
||||
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;
|
||||
else
|
||||
mCoeff = cw * -0.5f;
|
||||
|
||||
mLpZ1 = 0.0f;
|
||||
mLpZ2 = 0.0f;
|
||||
mApZ1 = 0.0f;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout, Real *lpout)
|
||||
{
|
||||
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 = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real
|
||||
{
|
||||
/* Low-pass sample processing. */
|
||||
Real d{(in - lp_z1) * lp_coeff};
|
||||
Real lp_y{lp_z1 + d};
|
||||
lp_z1 = lp_y + d;
|
||||
|
||||
d = (lp_y - lp_z2) * lp_coeff;
|
||||
lp_y = lp_z2 + d;
|
||||
lp_z2 = lp_y + d;
|
||||
|
||||
*(lpout++) = lp_y;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
ap_z1 = in - ap_y*ap_coeff;
|
||||
|
||||
/* High-pass generated from removing low-passed output. */
|
||||
return ap_y - lp_y;
|
||||
};
|
||||
std::transform(input.cbegin(), input.cend(), hpout, proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
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)
|
||||
{
|
||||
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(samples.begin(), samples.end(), samples.begin(), proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
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>::processAllPass(const al::span<Real> samples)
|
||||
{
|
||||
const Real coeff{mCoeff};
|
||||
Real z1{mApZ1};
|
||||
auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
|
||||
{
|
||||
const Real out{in*coeff + z1};
|
||||
z1 = in - out*coeff;
|
||||
return out;
|
||||
};
|
||||
std::transform(samples.cbegin(), samples.cend(), samples.begin(), proc_sample);
|
||||
mApZ1 = z1;
|
||||
}
|
||||
|
||||
|
||||
template class BandSplitterR<float>;
|
||||
template class BandSplitterR<double>;
|
||||
40
externals/openal-soft/core/filters/splitter.h
vendored
Normal file
40
externals/openal-soft/core/filters/splitter.h
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef CORE_FILTERS_SPLITTER_H
|
||||
#define CORE_FILTERS_SPLITTER_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
/* Band splitter. Splits a signal into two phase-matching frequency bands. */
|
||||
template<typename Real>
|
||||
class BandSplitterR {
|
||||
Real mCoeff{0.0f};
|
||||
Real mLpZ1{0.0f};
|
||||
Real mLpZ2{0.0f};
|
||||
Real mApZ1{0.0f};
|
||||
|
||||
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<const Real> input, Real *output, const Real hfscale);
|
||||
|
||||
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 or scaling the signal.
|
||||
*/
|
||||
void processAllPass(const al::span<Real> samples);
|
||||
};
|
||||
using BandSplitter = BandSplitterR<float>;
|
||||
|
||||
#endif /* CORE_FILTERS_SPLITTER_H */
|
||||
79
externals/openal-soft/core/fmt_traits.cpp
vendored
Normal file
79
externals/openal-soft/core/fmt_traits.cpp
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fmt_traits.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
const int16_t muLawDecompressionTable[256] = {
|
||||
-32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
|
||||
-23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
|
||||
-15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
|
||||
-11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
|
||||
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
|
||||
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
|
||||
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
|
||||
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
|
||||
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
|
||||
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
|
||||
-876, -844, -812, -780, -748, -716, -684, -652,
|
||||
-620, -588, -556, -524, -492, -460, -428, -396,
|
||||
-372, -356, -340, -324, -308, -292, -276, -260,
|
||||
-244, -228, -212, -196, -180, -164, -148, -132,
|
||||
-120, -112, -104, -96, -88, -80, -72, -64,
|
||||
-56, -48, -40, -32, -24, -16, -8, 0,
|
||||
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
|
||||
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
|
||||
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
|
||||
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
|
||||
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
|
||||
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
|
||||
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
|
||||
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
|
||||
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
|
||||
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
|
||||
876, 844, 812, 780, 748, 716, 684, 652,
|
||||
620, 588, 556, 524, 492, 460, 428, 396,
|
||||
372, 356, 340, 324, 308, 292, 276, 260,
|
||||
244, 228, 212, 196, 180, 164, 148, 132,
|
||||
120, 112, 104, 96, 88, 80, 72, 64,
|
||||
56, 48, 40, 32, 24, 16, 8, 0
|
||||
};
|
||||
|
||||
const int16_t aLawDecompressionTable[256] = {
|
||||
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
|
||||
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
|
||||
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
|
||||
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
|
||||
-22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
|
||||
-30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
|
||||
-11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
|
||||
-15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
|
||||
-344, -328, -376, -360, -280, -264, -312, -296,
|
||||
-472, -456, -504, -488, -408, -392, -440, -424,
|
||||
-88, -72, -120, -104, -24, -8, -56, -40,
|
||||
-216, -200, -248, -232, -152, -136, -184, -168,
|
||||
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
|
||||
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
|
||||
-688, -656, -752, -720, -560, -528, -624, -592,
|
||||
-944, -912, -1008, -976, -816, -784, -880, -848,
|
||||
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
|
||||
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
|
||||
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
|
||||
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
|
||||
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
|
||||
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
|
||||
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
|
||||
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
|
||||
344, 328, 376, 360, 280, 264, 312, 296,
|
||||
472, 456, 504, 488, 408, 392, 440, 424,
|
||||
88, 72, 120, 104, 24, 8, 56, 40,
|
||||
216, 200, 248, 232, 152, 136, 184, 168,
|
||||
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
|
||||
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
|
||||
688, 656, 752, 720, 560, 528, 624, 592,
|
||||
944, 912, 1008, 976, 816, 784, 880, 848
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
81
externals/openal-soft/core/fmt_traits.h
vendored
Normal file
81
externals/openal-soft/core/fmt_traits.h
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef CORE_FMT_TRAITS_H
|
||||
#define CORE_FMT_TRAITS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "buffer_storage.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
extern const int16_t muLawDecompressionTable[256];
|
||||
extern const int16_t aLawDecompressionTable[256];
|
||||
|
||||
|
||||
template<FmtType T>
|
||||
struct FmtTypeTraits { };
|
||||
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtUByte> {
|
||||
using Type = uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return val*OutT{1.0/128.0} - OutT{1.0}; }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtShort> {
|
||||
using Type = int16_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept { return val*OutT{1.0/32768.0}; }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtFloat> {
|
||||
using Type = float;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept { return val; }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtDouble> {
|
||||
using Type = double;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept { return static_cast<OutT>(val); }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtMulaw> {
|
||||
using Type = uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return muLawDecompressionTable[val] * OutT{1.0/32768.0}; }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtAlaw> {
|
||||
using Type = uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return aLawDecompressionTable[val] * OutT{1.0/32768.0}; }
|
||||
};
|
||||
|
||||
|
||||
template<FmtType SrcType, typename DstT>
|
||||
inline void LoadSampleArray(DstT *RESTRICT dst, const al::byte *src, const size_t srcstep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
using TypeTraits = FmtTypeTraits<SrcType>;
|
||||
using SampleType = typename TypeTraits::Type;
|
||||
|
||||
const SampleType *RESTRICT ssrc{reinterpret_cast<const SampleType*>(src)};
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
dst[i] = TypeTraits::template to<DstT>(ssrc[i*srcstep]);
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* CORE_FMT_TRAITS_H */
|
||||
61
externals/openal-soft/core/fpu_ctrl.cpp
vendored
Normal file
61
externals/openal-soft/core/fpu_ctrl.cpp
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fpu_ctrl.h"
|
||||
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#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"
|
||||
|
||||
|
||||
void FPUCtl::enter() noexcept
|
||||
{
|
||||
if(this->in_mode) return;
|
||||
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
this->sse_state = _mm_getcsr();
|
||||
unsigned int sseState{this->sse_state};
|
||||
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)
|
||||
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
{
|
||||
__asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state));
|
||||
unsigned int sseState{this->sse_state};
|
||||
sseState |= 0x8000; /* set flush-to-zero */
|
||||
if((CPUCapFlags&CPU_CAP_SSE2))
|
||||
sseState |= 0x0040; /* set denormals-are-zero */
|
||||
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState));
|
||||
}
|
||||
#endif
|
||||
|
||||
this->in_mode = true;
|
||||
}
|
||||
|
||||
void FPUCtl::leave() noexcept
|
||||
{
|
||||
if(!this->in_mode) return;
|
||||
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
_mm_setcsr(this->sse_state);
|
||||
|
||||
#elif defined(__GNUC__) && defined(HAVE_SSE)
|
||||
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state));
|
||||
#endif
|
||||
this->in_mode = false;
|
||||
}
|
||||
21
externals/openal-soft/core/fpu_ctrl.h
vendored
Normal file
21
externals/openal-soft/core/fpu_ctrl.h
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef CORE_FPU_CTRL_H
|
||||
#define CORE_FPU_CTRL_H
|
||||
|
||||
class FPUCtl {
|
||||
#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE))
|
||||
unsigned int sse_state{};
|
||||
#endif
|
||||
bool in_mode{};
|
||||
|
||||
public:
|
||||
FPUCtl() noexcept { enter(); in_mode = true; }
|
||||
~FPUCtl() { if(in_mode) leave(); }
|
||||
|
||||
FPUCtl(const FPUCtl&) = delete;
|
||||
FPUCtl& operator=(const FPUCtl&) = delete;
|
||||
|
||||
void enter() noexcept;
|
||||
void leave() noexcept;
|
||||
};
|
||||
|
||||
#endif /* CORE_FPU_CTRL_H */
|
||||
31
externals/openal-soft/core/front_stablizer.h
vendored
Normal file
31
externals/openal-soft/core/front_stablizer.h
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#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 {
|
||||
FrontStablizer(size_t numchans) : ChannelFilters{numchans} { }
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> MidDirect{};
|
||||
alignas(16) std::array<float,BufferLineSize> Side{};
|
||||
alignas(16) std::array<float,BufferLineSize> Temp{};
|
||||
|
||||
BandSplitter MidFilter;
|
||||
alignas(16) FloatBufferLine MidLF{};
|
||||
alignas(16) FloatBufferLine MidHF{};
|
||||
|
||||
al::FlexArray<BandSplitter,16> ChannelFilters;
|
||||
|
||||
static std::unique_ptr<FrontStablizer> Create(size_t numchans)
|
||||
{ return std::unique_ptr<FrontStablizer>{new(FamCount(numchans)) FrontStablizer{numchans}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(FrontStablizer, ChannelFilters)
|
||||
};
|
||||
|
||||
#endif /* CORE_FRONT_STABLIZER_H */
|
||||
569
externals/openal-soft/core/helpers.cpp
vendored
Normal file
569
externals/openal-soft/core/helpers.cpp
vendored
Normal file
@ -0,0 +1,569 @@
|
||||
|
||||
#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.emplace();
|
||||
return *procbin;
|
||||
}
|
||||
|
||||
fullpath.resize(len);
|
||||
if(fullpath.back() != 0)
|
||||
fullpath.push_back(0);
|
||||
|
||||
std::replace(fullpath.begin(), fullpath.end(), '/', '\\');
|
||||
auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
|
||||
if(sep != fullpath.rend())
|
||||
{
|
||||
*sep = 0;
|
||||
procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base())));
|
||||
}
|
||||
else
|
||||
procbin.emplace(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.emplace(std::string(pathname.cbegin(), sep.base()-1),
|
||||
std::string(sep.base(), pathname.cend()));
|
||||
else
|
||||
procbin.emplace(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);
|
||||
}
|
||||
|
||||
#ifdef ALSOFT_INSTALL_DATADIR
|
||||
// Search the installation data directory
|
||||
{
|
||||
std::string path{ALSOFT_INSTALL_DATADIR};
|
||||
if(!path.empty())
|
||||
{
|
||||
if(path.back() != '/')
|
||||
path += '/';
|
||||
path += subdir;
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool SetRTPriorityPthread(int prio)
|
||||
{
|
||||
int err{ENOTSUP};
|
||||
#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
|
||||
/* Get the min and max priority for SCHED_RR. Limit the max priority to
|
||||
* half, for now, to ensure the thread can't take the highest priority and
|
||||
* go rogue.
|
||||
*/
|
||||
int rtmin{sched_get_priority_min(SCHED_RR)};
|
||||
int rtmax{sched_get_priority_max(SCHED_RR)};
|
||||
rtmax = (rtmax-rtmin)/2 + rtmin;
|
||||
|
||||
struct sched_param param{};
|
||||
param.sched_priority = clampi(prio, rtmin, rtmax);
|
||||
#ifdef SCHED_RESET_ON_FORK
|
||||
err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m);
|
||||
if(err == EINVAL)
|
||||
#endif
|
||||
err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m);
|
||||
if(err == 0) return true;
|
||||
|
||||
#else
|
||||
|
||||
std::ignore = prio;
|
||||
#endif
|
||||
WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetRTPriorityRTKit(int prio)
|
||||
{
|
||||
#ifdef HAVE_RTKIT
|
||||
if(!HasDBus())
|
||||
{
|
||||
WARN("D-Bus not available\n");
|
||||
return false;
|
||||
}
|
||||
dbus::Error error;
|
||||
dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())};
|
||||
if(!conn)
|
||||
{
|
||||
WARN("D-Bus connection failed with %s: %s\n", error->name, error->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't stupidly exit if the connection dies while doing this. */
|
||||
dbus_connection_set_exit_on_disconnect(conn.get(), false);
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
static_cast<ulonglong>(rlim.rlim_max), static_cast<ulonglong>(rlim.rlim_cur));
|
||||
if(rlim.rlim_max > umaxtime)
|
||||
{
|
||||
rlim.rlim_max = static_cast<rlim_t>(std::min<ulonglong>(umaxtime,
|
||||
std::numeric_limits<rlim_t>::max()));
|
||||
rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
|
||||
if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if(rtmax > 0)
|
||||
{
|
||||
if(AllowRTTimeLimit)
|
||||
{
|
||||
err = limit_rttime(conn.get());
|
||||
if(err != 0)
|
||||
WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
|
||||
std::strerror(err), err);
|
||||
}
|
||||
|
||||
/* Limit the maximum real-time priority to half. */
|
||||
rtmax = (rtmax+1)/2;
|
||||
prio = clampi(prio, 1, rtmax);
|
||||
|
||||
TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax);
|
||||
err = rtkit_make_realtime(conn.get(), 0, prio);
|
||||
if(err == 0) return true;
|
||||
|
||||
err = std::abs(err);
|
||||
WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err);
|
||||
}
|
||||
/* Don't try to set the niceness for non-Linux systems. Standard POSIX has
|
||||
* niceness as a per-process attribute, while the intent here is for the
|
||||
* audio processing thread only to get a priority boost. Currently only
|
||||
* Linux is known to have per-thread niceness.
|
||||
*/
|
||||
#ifdef __linux__
|
||||
if(nicemin < 0)
|
||||
{
|
||||
TRACE("Making high priority with niceness %d\n", nicemin);
|
||||
err = rtkit_make_high_priority(conn.get(), 0, nicemin);
|
||||
if(err == 0) return true;
|
||||
|
||||
err = std::abs(err);
|
||||
WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err);
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
|
||||
#else
|
||||
|
||||
std::ignore = prio;
|
||||
WARN("D-Bus not supported\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetRTPriority()
|
||||
{
|
||||
if(RTPrioLevel <= 0)
|
||||
return;
|
||||
|
||||
if(SetRTPriorityPthread(RTPrioLevel))
|
||||
return;
|
||||
if(SetRTPriorityRTKit(RTPrioLevel))
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
18
externals/openal-soft/core/helpers.h
vendored
Normal file
18
externals/openal-soft/core/helpers.h
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef CORE_HELPERS_H
|
||||
#define CORE_HELPERS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
struct PathNamePair { std::string path, fname; };
|
||||
const PathNamePair &GetProcBinary(void);
|
||||
|
||||
extern int RTPrioLevel;
|
||||
extern bool AllowRTTimeLimit;
|
||||
void SetRTPriority(void);
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
|
||||
|
||||
#endif /* CORE_HELPERS_H */
|
||||
1473
externals/openal-soft/core/hrtf.cpp
vendored
Normal file
1473
externals/openal-soft/core/hrtf.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
89
externals/openal-soft/core/hrtf.h
vendored
Normal file
89
externals/openal-soft/core/hrtf.h
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
#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 mSampleRate : 24;
|
||||
uint mIrSize : 8;
|
||||
|
||||
struct Field {
|
||||
float distance;
|
||||
ubyte evCount;
|
||||
};
|
||||
/* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and
|
||||
* field[fdCount-1] is the nearest.
|
||||
*/
|
||||
al::span<const Field> mFields;
|
||||
|
||||
struct Elevation {
|
||||
ushort azCount;
|
||||
ushort irOffset;
|
||||
};
|
||||
Elevation *mElev;
|
||||
const HrirArray *mCoeffs;
|
||||
const ubyte2 *mDelays;
|
||||
|
||||
void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs,
|
||||
const al::span<uint,2> delays);
|
||||
|
||||
void add_ref();
|
||||
void dec_ref();
|
||||
|
||||
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 bool perHrirMin,
|
||||
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);
|
||||
|
||||
#endif /* CORE_HRTF_H */
|
||||
89
externals/openal-soft/core/logging.cpp
vendored
Normal file
89
externals/openal-soft/core/logging.cpp
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#elif defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
||||
{
|
||||
/* Kind of ugly since string literals are const char arrays with a size
|
||||
* that includes the null terminator, which we want to exclude from the
|
||||
* span.
|
||||
*/
|
||||
auto prefix = al::as_span("[ALSOFT] (--) ").first<14>();
|
||||
switch(level)
|
||||
{
|
||||
case LogLevel::Disable: break;
|
||||
case LogLevel::Error: prefix = al::as_span("[ALSOFT] (EE) ").first<14>(); break;
|
||||
case LogLevel::Warning: prefix = al::as_span("[ALSOFT] (WW) ").first<14>(); break;
|
||||
case LogLevel::Trace: prefix = al::as_span("[ALSOFT] (II) ").first<14>(); break;
|
||||
}
|
||||
|
||||
al::vector<char> dynmsg;
|
||||
std::array<char,256> stcmsg{};
|
||||
|
||||
char *str{stcmsg.data()};
|
||||
auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin());
|
||||
al::span<char> msg{prefend1, stcmsg.end()};
|
||||
|
||||
std::va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)};
|
||||
if(msglen >= 0 && static_cast<size_t>(msglen) >= msg.size()) UNLIKELY
|
||||
{
|
||||
dynmsg.resize(static_cast<size_t>(msglen)+prefix.size() + 1u);
|
||||
|
||||
str = dynmsg.data();
|
||||
auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin());
|
||||
msg = {prefend2, dynmsg.end()};
|
||||
|
||||
std::vsnprintf(msg.data(), msg.size(), fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
||||
if(gLogLevel >= level)
|
||||
{
|
||||
fputs(str, logfile);
|
||||
fflush(logfile);
|
||||
}
|
||||
#if defined(_WIN32) && !defined(NDEBUG)
|
||||
/* OutputDebugStringW has no 'level' property to distinguish between
|
||||
* informational, warning, or error debug messages. So only print them for
|
||||
* non-Release builds.
|
||||
*/
|
||||
std::wstring wstr{utf8_to_wstr(str)};
|
||||
OutputDebugStringW(wstr.c_str());
|
||||
#elif defined(__ANDROID__)
|
||||
auto android_severity = [](LogLevel l) noexcept
|
||||
{
|
||||
switch(l)
|
||||
{
|
||||
case LogLevel::Trace: return ANDROID_LOG_DEBUG;
|
||||
case LogLevel::Warning: return ANDROID_LOG_WARN;
|
||||
case LogLevel::Error: return ANDROID_LOG_ERROR;
|
||||
/* Should not happen. */
|
||||
case LogLevel::Disable:
|
||||
break;
|
||||
}
|
||||
return ANDROID_LOG_ERROR;
|
||||
};
|
||||
__android_log_print(android_severity(level), "openal", "%s", str);
|
||||
#endif
|
||||
}
|
||||
51
externals/openal-soft/core/logging.h
vendored
Normal file
51
externals/openal-soft/core/logging.h
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef CORE_LOGGING_H
|
||||
#define CORE_LOGGING_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
enum class LogLevel {
|
||||
Disable,
|
||||
Error,
|
||||
Warning,
|
||||
Trace
|
||||
};
|
||||
extern LogLevel gLogLevel;
|
||||
|
||||
extern FILE *gLogFile;
|
||||
|
||||
#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, ...);
|
||||
|
||||
#if (!defined(_WIN32) || defined(NDEBUG)) && !defined(__ANDROID__)
|
||||
#define TRACE(...) do { \
|
||||
if(gLogLevel >= LogLevel::Trace) UNLIKELY \
|
||||
al_print(LogLevel::Trace, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define WARN(...) do { \
|
||||
if(gLogLevel >= LogLevel::Warning) UNLIKELY \
|
||||
al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define ERR(...) do { \
|
||||
if(gLogLevel >= LogLevel::Error) UNLIKELY \
|
||||
al_print(LogLevel::Error, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define TRACE(...) al_print(LogLevel::Trace, gLogFile, __VA_ARGS__)
|
||||
|
||||
#define WARN(...) al_print(LogLevel::Warning, gLogFile, __VA_ARGS__)
|
||||
|
||||
#define ERR(...) al_print(LogLevel::Error, gLogFile, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif /* CORE_LOGGING_H */
|
||||
439
externals/openal-soft/core/mastering.cpp
vendored
Normal file
439
externals/openal-soft/core/mastering.cpp
vendored
Normal file
@ -0,0 +1,439 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mastering.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
/* These structures assume BufferLineSize is a power of 2. */
|
||||
static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2");
|
||||
|
||||
struct SlidingHold {
|
||||
alignas(16) float mValues[BufferLineSize];
|
||||
uint mExpiries[BufferLineSize];
|
||||
uint mLowerIndex;
|
||||
uint mUpperIndex;
|
||||
uint mLength;
|
||||
};
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
/* This sliding hold follows the input level with an instant attack and a
|
||||
* fixed duration hold before an instant release to the next highest level.
|
||||
* It is a sliding window maximum (descending maxima) implementation based on
|
||||
* Richard Harter's ascending minima algorithm available at:
|
||||
*
|
||||
* http://www.richardhartersworld.com/cri/2001/slidingmin.html
|
||||
*/
|
||||
float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
|
||||
{
|
||||
static constexpr uint mask{BufferLineSize - 1};
|
||||
const uint length{Hold->mLength};
|
||||
float (&values)[BufferLineSize] = Hold->mValues;
|
||||
uint (&expiries)[BufferLineSize] = Hold->mExpiries;
|
||||
uint lowerIndex{Hold->mLowerIndex};
|
||||
uint upperIndex{Hold->mUpperIndex};
|
||||
|
||||
if(i >= expiries[upperIndex])
|
||||
upperIndex = (upperIndex + 1) & mask;
|
||||
|
||||
if(in >= values[upperIndex])
|
||||
{
|
||||
values[upperIndex] = in;
|
||||
expiries[upperIndex] = i + length;
|
||||
lowerIndex = upperIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
do {
|
||||
do {
|
||||
if(!(in >= values[lowerIndex]))
|
||||
goto found_place;
|
||||
} while(lowerIndex--);
|
||||
lowerIndex = mask;
|
||||
} while(true);
|
||||
found_place:
|
||||
|
||||
lowerIndex = (lowerIndex + 1) & mask;
|
||||
values[lowerIndex] = in;
|
||||
expiries[lowerIndex] = i + length;
|
||||
}
|
||||
|
||||
Hold->mLowerIndex = lowerIndex;
|
||||
Hold->mUpperIndex = upperIndex;
|
||||
|
||||
return values[upperIndex];
|
||||
}
|
||||
|
||||
void ShiftSlidingHold(SlidingHold *Hold, const uint n)
|
||||
{
|
||||
auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex;
|
||||
auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex;
|
||||
if(exp_last-exp_begin < 0)
|
||||
{
|
||||
std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin,
|
||||
[n](uint e){ return e - n; });
|
||||
exp_begin = std::begin(Hold->mExpiries);
|
||||
}
|
||||
std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; });
|
||||
}
|
||||
|
||||
|
||||
/* Multichannel compression is linked via the absolute maximum of all
|
||||
* channels.
|
||||
*/
|
||||
void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer)
|
||||
{
|
||||
const size_t numChans{Comp->mNumChans};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
std::fill(side_begin, side_begin+SamplesToDo, 0.0f);
|
||||
|
||||
auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void
|
||||
{
|
||||
const float *RESTRICT buffer{al::assume_aligned<16>(input.data())};
|
||||
auto max_abs = std::bind(maxf, _1, std::bind(static_cast<float(&)(float)>(std::fabs), _2));
|
||||
std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs);
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, fill_max);
|
||||
}
|
||||
|
||||
/* This calculates the squared crest factor of the control signal for the
|
||||
* basic automation of the attack/release times. As suggested by the paper,
|
||||
* it uses an instantaneous squared peak detector and a squared RMS detector
|
||||
* both with 200ms release times.
|
||||
*/
|
||||
void CrestDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
{
|
||||
const float a_crest{Comp->mCrestCoeff};
|
||||
float y2_peak{Comp->mLastPeakSq};
|
||||
float y2_rms{Comp->mLastRmsSq};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float
|
||||
{
|
||||
const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)};
|
||||
|
||||
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;
|
||||
std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest);
|
||||
|
||||
Comp->mLastPeakSq = y2_peak;
|
||||
Comp->mLastRmsSq = y2_rms;
|
||||
}
|
||||
|
||||
/* The side-chain starts with a simple peak detector (based on the absolute
|
||||
* value of the incoming signal) and performs most of its operations in the
|
||||
* log domain.
|
||||
*/
|
||||
void PeakDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* Clamp the minimum amplitude to near-zero and convert to logarithm. */
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
std::transform(side_begin, side_begin+SamplesToDo, side_begin,
|
||||
[](float s) { return std::log(maxf(0.000001f, s)); });
|
||||
}
|
||||
|
||||
/* An optional hold can be used to extend the peak detector so it can more
|
||||
* solidly detect fast transients. This is best used when operating as a
|
||||
* limiter.
|
||||
*/
|
||||
void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
SlidingHold *hold{Comp->mHold};
|
||||
uint i{0};
|
||||
auto detect_peak = [&i,hold](const float x_abs) -> float
|
||||
{
|
||||
const float x_G{std::log(maxf(0.000001f, x_abs))};
|
||||
return UpdateSlidingHold(hold, i++, x_G);
|
||||
};
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak);
|
||||
|
||||
ShiftSlidingHold(hold, SamplesToDo);
|
||||
}
|
||||
|
||||
/* This is the heart of the feed-forward compressor. It operates in the log
|
||||
* domain (to better match human hearing) and can apply some basic automation
|
||||
* to knee width, attack/release times, make-up/post gain, and clipping
|
||||
* reduction.
|
||||
*/
|
||||
void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
||||
{
|
||||
const bool autoKnee{Comp->mAuto.Knee};
|
||||
const bool autoAttack{Comp->mAuto.Attack};
|
||||
const bool autoRelease{Comp->mAuto.Release};
|
||||
const bool autoPostGain{Comp->mAuto.PostGain};
|
||||
const bool autoDeclip{Comp->mAuto.Declip};
|
||||
const uint lookAhead{Comp->mLookAhead};
|
||||
const float threshold{Comp->mThreshold};
|
||||
const float slope{Comp->mSlope};
|
||||
const float attack{Comp->mAttack};
|
||||
const float release{Comp->mRelease};
|
||||
const float c_est{Comp->mGainEstimate};
|
||||
const float a_adp{Comp->mAdaptCoeff};
|
||||
const float *crestFactor{Comp->mCrestFactor};
|
||||
float postGain{Comp->mPostGain};
|
||||
float knee{Comp->mKnee};
|
||||
float t_att{attack};
|
||||
float t_rel{release - attack};
|
||||
float a_att{std::exp(-1.0f / t_att)};
|
||||
float a_rel{std::exp(-1.0f / t_rel)};
|
||||
float y_1{Comp->mLastRelease};
|
||||
float y_L{Comp->mLastAttack};
|
||||
float c_dev{Comp->mLastGainDev};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(float &sideChain : al::span<float>{Comp->mSideChain, SamplesToDo})
|
||||
{
|
||||
if(autoKnee)
|
||||
knee = maxf(0.0f, 2.5f * (c_dev + c_est));
|
||||
const float knee_h{0.5f * knee};
|
||||
|
||||
/* This is the gain computer. It applies a static compression curve
|
||||
* to the control signal.
|
||||
*/
|
||||
const float x_over{std::addressof(sideChain)[lookAhead] - threshold};
|
||||
const float y_G{
|
||||
(x_over <= -knee_h) ? 0.0f :
|
||||
(std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) :
|
||||
x_over};
|
||||
|
||||
const float y2_crest{*(crestFactor++)};
|
||||
if(autoAttack)
|
||||
{
|
||||
t_att = 2.0f*attack/y2_crest;
|
||||
a_att = std::exp(-1.0f / t_att);
|
||||
}
|
||||
if(autoRelease)
|
||||
{
|
||||
t_rel = 2.0f*release/y2_crest - t_att;
|
||||
a_rel = std::exp(-1.0f / t_rel);
|
||||
}
|
||||
|
||||
/* Gain smoothing (ballistics) is done via a smooth decoupled peak
|
||||
* detector. The attack time is subtracted from the release time
|
||||
* above to compensate for the chained operating mode.
|
||||
*/
|
||||
const float x_L{-slope * y_G};
|
||||
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 = lerpf(-(y_L+c_est), c_dev, a_adp);
|
||||
|
||||
if(autoPostGain)
|
||||
{
|
||||
/* Clipping reduction is only viable when make-up gain is being
|
||||
* automated. It modifies the deviation to further attenuate the
|
||||
* control signal when clipping is detected. The adaptation time
|
||||
* is sufficiently long enough to suppress further clipping at the
|
||||
* same output level.
|
||||
*/
|
||||
if(autoDeclip)
|
||||
c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est);
|
||||
|
||||
postGain = -(c_dev + c_est);
|
||||
}
|
||||
|
||||
sideChain = std::exp(postGain - y_L);
|
||||
}
|
||||
|
||||
Comp->mLastRelease = y_1;
|
||||
Comp->mLastAttack = y_L;
|
||||
Comp->mLastGainDev = c_dev;
|
||||
}
|
||||
|
||||
/* Combined with the hold time, a look-ahead delay can improve handling of
|
||||
* fast transients by allowing the envelope time to converge prior to
|
||||
* reaching the offending impulse. This is best used when operating as a
|
||||
* limiter.
|
||||
*/
|
||||
void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
||||
{
|
||||
const size_t numChans{Comp->mNumChans};
|
||||
const uint lookAhead{Comp->mLookAhead};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
ASSUME(lookAhead > 0);
|
||||
|
||||
for(size_t c{0};c < numChans;c++)
|
||||
{
|
||||
float *inout{al::assume_aligned<16>(OutBuffer[c].data())};
|
||||
float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())};
|
||||
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if(SamplesToDo >= lookAhead) LIKELY
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end);
|
||||
std::swap_ranges(inout, delay_end, delaybuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(inout, inout_end, delaybuf);
|
||||
std::rotate(delaybuf, delay_start, delaybuf + lookAhead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const float SampleRate,
|
||||
const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain,
|
||||
const bool AutoDeclip, const float LookAheadTime, const float HoldTime, const float PreGainDb,
|
||||
const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb,
|
||||
const float AttackTime, const float ReleaseTime)
|
||||
{
|
||||
const auto lookAhead = static_cast<uint>(
|
||||
clampf(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1));
|
||||
const auto hold = static_cast<uint>(
|
||||
clampf(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1));
|
||||
|
||||
size_t size{sizeof(Compressor)};
|
||||
if(lookAhead > 0)
|
||||
{
|
||||
size += sizeof(*Compressor::mDelay) * NumChans;
|
||||
/* The sliding hold implementation doesn't handle a length of 1. A 1-
|
||||
* sample hold is useless anyway, it would only ever give back what was
|
||||
* just given to it.
|
||||
*/
|
||||
if(hold > 1)
|
||||
size += sizeof(*Compressor::mHold);
|
||||
}
|
||||
|
||||
auto Comp = CompressorPtr{al::construct_at(static_cast<Compressor*>(al_calloc(16, size)))};
|
||||
Comp->mNumChans = NumChans;
|
||||
Comp->mAuto.Knee = AutoKnee;
|
||||
Comp->mAuto.Attack = AutoAttack;
|
||||
Comp->mAuto.Release = AutoRelease;
|
||||
Comp->mAuto.PostGain = AutoPostGain;
|
||||
Comp->mAuto.Declip = AutoPostGain && AutoDeclip;
|
||||
Comp->mLookAhead = lookAhead;
|
||||
Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f);
|
||||
Comp->mPostGain = PostGainDb * std::log(10.0f) / 20.0f;
|
||||
Comp->mThreshold = ThresholdDb * std::log(10.0f) / 20.0f;
|
||||
Comp->mSlope = 1.0f / maxf(1.0f, Ratio) - 1.0f;
|
||||
Comp->mKnee = maxf(0.0f, KneeDb * std::log(10.0f) / 20.0f);
|
||||
Comp->mAttack = maxf(1.0f, AttackTime * SampleRate);
|
||||
Comp->mRelease = maxf(1.0f, ReleaseTime * SampleRate);
|
||||
|
||||
/* Knee width automation actually treats the compressor as a limiter. By
|
||||
* varying the knee width, it can effectively be seen as applying
|
||||
* compression over a wide range of ratios.
|
||||
*/
|
||||
if(AutoKnee)
|
||||
Comp->mSlope = -1.0f;
|
||||
|
||||
if(lookAhead > 0)
|
||||
{
|
||||
if(hold > 1)
|
||||
{
|
||||
Comp->mHold = 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 = reinterpret_cast<FloatBufferLine*>(Comp->mHold + 1);
|
||||
}
|
||||
else
|
||||
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
|
||||
Comp->mGainEstimate = Comp->mThreshold * -0.5f * Comp->mSlope;
|
||||
Comp->mAdaptCoeff = std::exp(-1.0f / (2.0f * SampleRate)); // 2s
|
||||
|
||||
return Comp;
|
||||
}
|
||||
|
||||
Compressor::~Compressor()
|
||||
{
|
||||
if(mHold)
|
||||
al::destroy_at(mHold);
|
||||
mHold = nullptr;
|
||||
if(mDelay)
|
||||
al::destroy_n(mDelay, mNumChans);
|
||||
mDelay = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
||||
{
|
||||
const size_t numChans{mNumChans};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
const float preGain{mPreGain};
|
||||
if(preGain != 1.0f)
|
||||
{
|
||||
auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void
|
||||
{
|
||||
float *buffer{al::assume_aligned<16>(input.data())};
|
||||
std::transform(buffer, buffer+SamplesToDo, buffer,
|
||||
[preGain](float s) { return s * preGain; });
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_gain);
|
||||
}
|
||||
|
||||
LinkChannels(this, SamplesToDo, OutBuffer);
|
||||
|
||||
if(mAuto.Attack || mAuto.Release)
|
||||
CrestDetector(this, SamplesToDo);
|
||||
|
||||
if(mHold)
|
||||
PeakHoldDetector(this, SamplesToDo);
|
||||
else
|
||||
PeakDetector(this, SamplesToDo);
|
||||
|
||||
GainCompressor(this, SamplesToDo);
|
||||
|
||||
if(mDelay)
|
||||
SignalDelay(this, SamplesToDo, OutBuffer);
|
||||
|
||||
const float (&sideChain)[BufferLineSize*2] = mSideChain;
|
||||
auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void
|
||||
{
|
||||
float *buffer{al::assume_aligned<16>(input.data())};
|
||||
const float *gains{al::assume_aligned<16>(&sideChain[0])};
|
||||
std::transform(gains, gains+SamplesToDo, buffer, buffer,
|
||||
[](float g, float s) { return g * s; });
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
|
||||
|
||||
auto side_begin = std::begin(mSideChain) + SamplesToDo;
|
||||
std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain));
|
||||
}
|
||||
105
externals/openal-soft/core/mastering.h
vendored
Normal file
105
externals/openal-soft/core/mastering.h
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef CORE_MASTERING_H
|
||||
#define CORE_MASTERING_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "bufferline.h"
|
||||
|
||||
struct SlidingHold;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
/* General topology and basic automation was based on the following paper:
|
||||
*
|
||||
* D. Giannoulis, M. Massberg and J. D. Reiss,
|
||||
* "Parameter Automation in a Dynamic Range Compressor,"
|
||||
* Journal of the Audio Engineering Society, v61 (10), Oct. 2013
|
||||
*
|
||||
* Available (along with supplemental reading) at:
|
||||
*
|
||||
* http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
|
||||
*/
|
||||
struct Compressor {
|
||||
size_t mNumChans{0u};
|
||||
|
||||
struct {
|
||||
bool Knee : 1;
|
||||
bool Attack : 1;
|
||||
bool Release : 1;
|
||||
bool PostGain : 1;
|
||||
bool Declip : 1;
|
||||
} mAuto{};
|
||||
|
||||
uint mLookAhead{0};
|
||||
|
||||
float mPreGain{0.0f};
|
||||
float mPostGain{0.0f};
|
||||
|
||||
float mThreshold{0.0f};
|
||||
float mSlope{0.0f};
|
||||
float mKnee{0.0f};
|
||||
|
||||
float mAttack{0.0f};
|
||||
float mRelease{0.0f};
|
||||
|
||||
alignas(16) float mSideChain[2*BufferLineSize]{};
|
||||
alignas(16) float mCrestFactor[BufferLineSize]{};
|
||||
|
||||
SlidingHold *mHold{nullptr};
|
||||
FloatBufferLine *mDelay{nullptr};
|
||||
|
||||
float mCrestCoeff{0.0f};
|
||||
float mGainEstimate{0.0f};
|
||||
float mAdaptCoeff{0.0f};
|
||||
|
||||
float mLastPeakSq{0.0f};
|
||||
float mLastRmsSq{0.0f};
|
||||
float mLastRelease{0.0f};
|
||||
float mLastAttack{0.0f};
|
||||
float mLastGainDev{0.0f};
|
||||
|
||||
|
||||
~Compressor();
|
||||
void process(const uint SamplesToDo, FloatBufferLine *OutBuffer);
|
||||
int getLookAhead() const noexcept { return static_cast<int>(mLookAhead); }
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
|
||||
/**
|
||||
* The compressor is initialized with the following settings:
|
||||
*
|
||||
* \param NumChans Number of channels to process.
|
||||
* \param SampleRate Sample rate to process.
|
||||
* \param AutoKnee Whether to automate the knee width parameter.
|
||||
* \param AutoAttack Whether to automate the attack time parameter.
|
||||
* \param AutoRelease Whether to automate the release time parameter.
|
||||
* \param AutoPostGain Whether to automate the make-up (post) gain
|
||||
* parameter.
|
||||
* \param AutoDeclip Whether to automate clipping reduction. Ignored
|
||||
* when not automating make-up gain.
|
||||
* \param LookAheadTime Look-ahead time (in seconds).
|
||||
* \param HoldTime Peak hold-time (in seconds).
|
||||
* \param PreGainDb Gain applied before detection (in dB).
|
||||
* \param PostGainDb Make-up gain applied after compression (in dB).
|
||||
* \param ThresholdDb Triggering threshold (in dB).
|
||||
* \param Ratio Compression ratio (x:1). Set to INFINIFTY for true
|
||||
* limiting. Ignored when automating knee width.
|
||||
* \param KneeDb Knee width (in dB). Ignored when automating knee
|
||||
* width.
|
||||
* \param AttackTime Attack time (in seconds). Acts as a maximum when
|
||||
* automating attack time.
|
||||
* \param ReleaseTime Release time (in seconds). Acts as a maximum when
|
||||
* automating release time.
|
||||
*/
|
||||
static std::unique_ptr<Compressor> Create(const size_t NumChans, const float SampleRate,
|
||||
const bool AutoKnee, const bool AutoAttack, const bool AutoRelease,
|
||||
const bool AutoPostGain, const bool AutoDeclip, const float LookAheadTime,
|
||||
const float HoldTime, const float PreGainDb, const float PostGainDb,
|
||||
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 */
|
||||
95
externals/openal-soft/core/mixer.cpp
vendored
Normal file
95
externals/openal-soft/core/mixer.cpp
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mixer.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "devformat.h"
|
||||
#include "device.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
struct CTag;
|
||||
|
||||
|
||||
MixerOutFunc MixSamplesOut{Mix_<CTag>};
|
||||
MixerOneFunc MixSamplesOne{Mix_<CTag>};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
|
||||
const float spread)
|
||||
{
|
||||
std::array<float,MaxAmbiChannels> coeffs{CalcAmbiCoeffs(y, z, x)};
|
||||
|
||||
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,MaxAmbiChannels> 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);
|
||||
}
|
||||
109
externals/openal-soft/core/mixer.h
vendored
Normal file
109
externals/openal-soft/core/mixer.h
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
#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;
|
||||
|
||||
/* Mixer functions that handle one input and multiple output channels. */
|
||||
using MixerOutFunc = 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 MixerOutFunc MixSamplesOut;
|
||||
inline void MixSamples(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos)
|
||||
{ MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); }
|
||||
|
||||
/* Mixer functions that handle one input and one output channel. */
|
||||
using MixerOneFunc = void(*)(const al::span<const float> InSamples, float *OutBuffer,
|
||||
float &CurrentGain, const float TargetGain, const size_t Counter);
|
||||
|
||||
extern MixerOneFunc MixSamplesOne;
|
||||
inline void MixSamples(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{ MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); }
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length).
|
||||
*/
|
||||
constexpr std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[3])
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,MaxAmbiChannels> gains);
|
||||
|
||||
#endif /* CORE_MIXER_H */
|
||||
109
externals/openal-soft/core/mixer/defs.h
vendored
Normal file
109
externals/openal-soft/core/mixer/defs.h
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
#ifndef CORE_MIXER_DEFS_H
|
||||
#define CORE_MIXER_DEFS_H
|
||||
|
||||
#include <array>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/resampler_limits.h"
|
||||
|
||||
struct CubicCoefficients;
|
||||
struct HrtfChannelState;
|
||||
struct HrtfFilter;
|
||||
struct MixHrtfFilter;
|
||||
|
||||
using uint = unsigned int;
|
||||
using float2 = std::array<float,2>;
|
||||
|
||||
|
||||
constexpr int MixerFracBits{16};
|
||||
constexpr int MixerFracOne{1 << MixerFracBits};
|
||||
constexpr int MixerFracMask{MixerFracOne - 1};
|
||||
constexpr int MixerFracHalf{MixerFracOne >> 1};
|
||||
|
||||
constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
|
||||
|
||||
enum class Resampler : uint8_t {
|
||||
Point,
|
||||
Linear,
|
||||
Cubic,
|
||||
FastBSinc12,
|
||||
BSinc12,
|
||||
FastBSinc24,
|
||||
BSinc24,
|
||||
|
||||
Max = BSinc24
|
||||
};
|
||||
|
||||
/* Interpolator state. Kind of a misnomer since the interpolator itself is
|
||||
* stateless. This just keeps it from having to recompute scale-related
|
||||
* mappings for every sample.
|
||||
*/
|
||||
struct BsincState {
|
||||
float sf; /* Scale interpolation factor. */
|
||||
uint m; /* Coefficient count. */
|
||||
uint l; /* Left coefficient offset. */
|
||||
/* Filter coefficients, followed by the phase, scale, and scale-phase
|
||||
* delta coefficients. Starting at phase index 0, each subsequent phase
|
||||
* index follows contiguously.
|
||||
*/
|
||||
const float *filter;
|
||||
};
|
||||
|
||||
struct CubicState {
|
||||
/* Filter coefficients, and coefficient deltas. Starting at phase index 0,
|
||||
* each subsequent phase index follows contiguously.
|
||||
*/
|
||||
const CubicCoefficients *filter;
|
||||
};
|
||||
|
||||
union InterpState {
|
||||
CubicState cubic;
|
||||
BsincState bsinc;
|
||||
};
|
||||
|
||||
using ResamplerFunc = void(*)(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst);
|
||||
|
||||
ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state);
|
||||
|
||||
|
||||
template<typename TypeTag, typename InstTag>
|
||||
void Resample_(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst);
|
||||
|
||||
template<typename InstTag>
|
||||
void Mix_(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
|
||||
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos);
|
||||
template<typename InstTag>
|
||||
void Mix_(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter);
|
||||
|
||||
template<typename InstTag>
|
||||
void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize);
|
||||
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_(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);
|
||||
|
||||
/* Vectorized resampler helpers */
|
||||
template<size_t N>
|
||||
inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N])
|
||||
{
|
||||
pos_arr[0] = 0;
|
||||
frac_arr[0] = frac;
|
||||
for(size_t i{1};i < N;i++)
|
||||
{
|
||||
const uint frac_tmp{frac_arr[i-1] + increment};
|
||||
pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits);
|
||||
frac_arr[i] = frac_tmp&MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CORE_MIXER_DEFS_H */
|
||||
129
externals/openal-soft/core/mixer/hrtfbase.h
vendored
Normal file
129
externals/openal-soft/core/mixer/hrtfbase.h
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
#ifndef CORE_MIXER_HRTFBASE_H
|
||||
#define CORE_MIXER_HRTFBASE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "hrtfdefs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize,
|
||||
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,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
const ConstHrirSpan Coeffs{hrtfparams->Coeffs};
|
||||
const float gainstep{hrtfparams->GainStep};
|
||||
const float gain{hrtfparams->Gain};
|
||||
|
||||
size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]};
|
||||
size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]};
|
||||
float stepcount{0.0f};
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
{
|
||||
const float g{gain + gainstep*stepcount};
|
||||
const float left{InSamples[ldelay++] * g};
|
||||
const float right{InSamples[rdelay++] * g};
|
||||
ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right);
|
||||
|
||||
stepcount += 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
template<ApplyCoeffsT ApplyCoeffs>
|
||||
inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples,
|
||||
const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
|
||||
const size_t BufferSize)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
const ConstHrirSpan OldCoeffs{oldparams->Coeffs};
|
||||
const float oldGainStep{oldparams->Gain / static_cast<float>(BufferSize)};
|
||||
const ConstHrirSpan NewCoeffs{newparams->Coeffs};
|
||||
const float newGainStep{newparams->GainStep};
|
||||
|
||||
if(oldparams->Gain > GainSilenceThreshold) LIKELY
|
||||
{
|
||||
size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]};
|
||||
size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]};
|
||||
auto stepcount = static_cast<float>(BufferSize);
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
{
|
||||
const float g{oldGainStep*stepcount};
|
||||
const float left{InSamples[ldelay++] * g};
|
||||
const float right{InSamples[rdelay++] * g};
|
||||
ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right);
|
||||
|
||||
stepcount -= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if(newGainStep*static_cast<float>(BufferSize) > GainSilenceThreshold) LIKELY
|
||||
{
|
||||
size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]};
|
||||
size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]};
|
||||
float stepcount{1.0f};
|
||||
for(size_t i{1u};i < BufferSize;++i)
|
||||
{
|
||||
const float g{newGainStep*stepcount};
|
||||
const float left{InSamples[ldelay++] * g};
|
||||
const float right{InSamples[rdelay++] * g};
|
||||
ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right);
|
||||
|
||||
stepcount += 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<ApplyCoeffsT ApplyCoeffs>
|
||||
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);
|
||||
|
||||
for(const FloatBufferLine &input : InSamples)
|
||||
{
|
||||
/* For dual-band processing, the signal needs extra scaling applied to
|
||||
* the high frequency response. The band-splitter applies this scaling
|
||||
* with a consistent phase shift regardless of the scale amount.
|
||||
*/
|
||||
ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf,
|
||||
ChanState->mHfScale);
|
||||
|
||||
/* Now apply the HRIR coefficients to this channel. */
|
||||
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]};
|
||||
ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample);
|
||||
}
|
||||
|
||||
++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)
|
||||
left[i] += AccumSamples[i][0];
|
||||
for(size_t i{0u};i < BufferSize;++i)
|
||||
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, AccumSamples);
|
||||
std::fill_n(accum_iter, BufferSize, float2{});
|
||||
}
|
||||
|
||||
#endif /* CORE_MIXER_HRTFBASE_H */
|
||||
53
externals/openal-soft/core/mixer/hrtfdefs.h
vendored
Normal file
53
externals/openal-soft/core/mixer/hrtfdefs.h
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef CORE_MIXER_HRTFDEFS_H
|
||||
#define CORE_MIXER_HRTFDEFS_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/filters/splitter.h"
|
||||
|
||||
|
||||
using float2 = std::array<float,2>;
|
||||
using ubyte = unsigned char;
|
||||
using ubyte2 = std::array<ubyte,2>;
|
||||
using ushort = unsigned short;
|
||||
using uint = unsigned int;
|
||||
using uint2 = std::array<uint,2>;
|
||||
|
||||
constexpr uint HrtfHistoryBits{6};
|
||||
constexpr uint HrtfHistoryLength{1 << HrtfHistoryBits};
|
||||
constexpr uint HrtfHistoryMask{HrtfHistoryLength - 1};
|
||||
|
||||
constexpr uint HrirBits{7};
|
||||
constexpr uint HrirLength{1 << HrirBits};
|
||||
constexpr uint HrirMask{HrirLength - 1};
|
||||
|
||||
constexpr uint MinIrLength{8};
|
||||
|
||||
using HrirArray = std::array<float2,HrirLength>;
|
||||
using HrirSpan = al::span<float2,HrirLength>;
|
||||
using ConstHrirSpan = al::span<const float2,HrirLength>;
|
||||
|
||||
struct MixHrtfFilter {
|
||||
const ConstHrirSpan Coeffs;
|
||||
uint2 Delay;
|
||||
float Gain;
|
||||
float GainStep;
|
||||
};
|
||||
|
||||
struct HrtfFilter {
|
||||
alignas(16) HrirArray Coeffs;
|
||||
uint2 Delay;
|
||||
float Gain;
|
||||
};
|
||||
|
||||
|
||||
struct HrtfChannelState {
|
||||
BandSplitter mSplitter;
|
||||
float mHfScale{};
|
||||
alignas(16) HrirArray mCoeffs{};
|
||||
};
|
||||
|
||||
#endif /* CORE_MIXER_HRTFDEFS_H */
|
||||
218
externals/openal-soft/core/mixer/mixer_c.cpp
vendored
Normal file
218
externals/openal-soft/core/mixer/mixer_c.cpp
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct CTag;
|
||||
struct PointTag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
struct BSincTag;
|
||||
struct FastBSincTag;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint BsincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BsincPhaseDiffOne{1 << BsincPhaseDiffBits};
|
||||
constexpr uint BsincPhaseDiffMask{BsincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
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 lerpf(vals[0], vals[1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
inline float do_cubic(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
{
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
|
||||
const float *RESTRICT fil{al::assume_aligned<16>(istate.cubic.filter[pi].mCoeffs)};
|
||||
const float *RESTRICT phd{al::assume_aligned<16>(istate.cubic.filter[pi].mDeltas)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
return (fil[0] + pf*phd[0])*vals[0] + (fil[1] + pf*phd[1])*vals[1]
|
||||
+ (fil[2] + pf*phd[2])*vals[2] + (fil[3] + pf*phd[3])*vals[3];
|
||||
}
|
||||
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 >> BsincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)};
|
||||
|
||||
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};
|
||||
for(size_t j_f{0};j_f < m;j_f++)
|
||||
r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f];
|
||||
return r;
|
||||
}
|
||||
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 >> BsincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)};
|
||||
|
||||
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};
|
||||
for(size_t j_f{0};j_f < m;j_f++)
|
||||
r += (fil[j_f] + pf*phd[j_f]) * vals[j_f];
|
||||
return r;
|
||||
}
|
||||
|
||||
using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint);
|
||||
template<SamplerT Sampler>
|
||||
void DoResample(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const InterpState istate{*state};
|
||||
ASSUME(frac < MixerFracOne);
|
||||
for(float &out : dst)
|
||||
{
|
||||
out = Sampler(istate, src, frac);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
ASSUME(IrSize >= MinIrLength);
|
||||
for(size_t c{0};c < IrSize;++c)
|
||||
{
|
||||
Values[c][0] += Coeffs[c][0] * left;
|
||||
Values[c][1] += Coeffs[c][1] * right;
|
||||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
for(;pos != min_len;++pos)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
for(;pos != InSamples.size();++pos)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<PointTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_point>(state, src, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_lerp>(state, src, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_cubic>(state, src-1, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<FastBSincTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_fastbsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
|
||||
|
||||
template<>
|
||||
void MixHrtf_<CTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<CTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
}
|
||||
|
||||
template<>
|
||||
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)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
void Mix_<CTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
|
||||
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<CTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain,
|
||||
TargetGain, delta, min_len, Counter);
|
||||
}
|
||||
362
externals/openal-soft/core/mixer/mixer_neon.cpp
vendored
Normal file
362
externals/openal-soft/core/mixer/mixer_neon.cpp
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct NEONTag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
struct BSincTag;
|
||||
struct FastBSincTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ARM_NEON)
|
||||
#pragma GCC target("fpu=neon")
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits};
|
||||
constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(l0)};
|
||||
ret = vsetq_lane_f32(l1, ret, 1);
|
||||
ret = vsetq_lane_f32(l2, ret, 2);
|
||||
ret = vsetq_lane_f32(l3, ret, 3);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
float32x4_t leftright4;
|
||||
{
|
||||
float32x2_t leftright2{vmov_n_f32(left)};
|
||||
leftright2 = vset_lane_f32(right, leftright2, 1);
|
||||
leftright4 = vcombine_f32(leftright2, leftright2);
|
||||
}
|
||||
|
||||
ASSUME(IrSize >= MinIrLength);
|
||||
for(size_t c{0};c < IrSize;c += 2)
|
||||
{
|
||||
float32x4_t vals = vld1q_f32(&Values[c][0]);
|
||||
float32x4_t coefs = vld1q_f32(&Coeffs[c][0]);
|
||||
|
||||
vals = vmlaq_f32(vals, coefs, leftright4);
|
||||
|
||||
vst1q_f32(&Values[c][0], vals);
|
||||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
const size_t aligned_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const float32x4_t four4{vdupq_n_f32(4.0f)};
|
||||
const float32x4_t step4{vdupq_n_f32(step)};
|
||||
const float32x4_t gain4{vdupq_n_f32(gain)};
|
||||
float32x4_t step_count4{vdupq_n_f32(0.0f)};
|
||||
step_count4 = vsetq_lane_f32(1.0f, step_count4, 1);
|
||||
step_count4 = vsetq_lane_f32(2.0f, step_count4, 2);
|
||||
step_count4 = vsetq_lane_f32(3.0f, step_count4, 3);
|
||||
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
|
||||
step_count4 = vaddq_f32(step_count4, four4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after the
|
||||
* last four mixed samples, so the lowest element represents the
|
||||
* next step count to apply.
|
||||
*/
|
||||
step_count = vgetq_lane_f32(step_count4, 0);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const float32x4_t gain4 = vdupq_n_f32(gain);
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, gain4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,NEONTag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const int32x4_t increment4 = vdupq_n_s32(static_cast<int>(increment*4));
|
||||
const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne);
|
||||
const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask);
|
||||
alignas(16) uint pos_[4], frac_[4];
|
||||
int32x4_t pos4, frac4;
|
||||
|
||||
InitPosArrays(frac, increment, frac_, pos_);
|
||||
frac4 = vld1q_s32(reinterpret_cast<int*>(frac_));
|
||||
pos4 = vld1q_s32(reinterpret_cast<int*>(pos_));
|
||||
|
||||
auto dst_iter = dst.begin();
|
||||
for(size_t todo{dst.size()>>2};todo;--todo)
|
||||
{
|
||||
const int pos0{vgetq_lane_s32(pos4, 0)};
|
||||
const int pos1{vgetq_lane_s32(pos4, 1)};
|
||||
const int pos2{vgetq_lane_s32(pos4, 2)};
|
||||
const int pos3{vgetq_lane_s32(pos4, 3)};
|
||||
const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])};
|
||||
const float32x4_t val2{set_f4(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
|
||||
|
||||
/* val1 + (val2-val1)*mu */
|
||||
const float32x4_t r0{vsubq_f32(val2, val1)};
|
||||
const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)};
|
||||
const float32x4_t out{vmlaq_f32(val1, mu, r0)};
|
||||
|
||||
vst1q_f32(dst_iter, out);
|
||||
dst_iter += 4;
|
||||
|
||||
frac4 = vaddq_s32(frac4, increment4);
|
||||
pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, MixerFracBits));
|
||||
frac4 = vandq_s32(frac4, fracMask4);
|
||||
}
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
{
|
||||
src += static_cast<uint>(vgetq_lane_s32(pos4, 0));
|
||||
frac = static_cast<uint>(vgetq_lane_s32(frac4, 0));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter);
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
|
||||
/* f = fil + pf*phd */
|
||||
const float32x4_t f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs), pf4,
|
||||
vld1q_f32(filter[pi].mDeltas));
|
||||
/* r = f*src */
|
||||
float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))};
|
||||
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
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);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
{
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
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};
|
||||
|
||||
do {
|
||||
/* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
|
||||
const float32x4_t f4 = vmlaq_f32(
|
||||
vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])),
|
||||
pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j])));
|
||||
/* r += f*src */
|
||||
r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
|
||||
j += 4;
|
||||
} while(--td);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
{
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
do {
|
||||
/* f = fil + pf*phd */
|
||||
const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j]));
|
||||
/* r += f*src */
|
||||
r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
|
||||
j += 4;
|
||||
} while(--td);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
void MixHrtf_<NEONTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
}
|
||||
|
||||
template<>
|
||||
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)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
|
||||
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
}
|
||||
327
externals/openal-soft/core/mixer/mixer_sse.cpp
vendored
Normal file
327
externals/openal-soft/core/mixer/mixer_sse.cpp
vendored
Normal file
@ -0,0 +1,327 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <xmmintrin.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct SSETag;
|
||||
struct CubicTag;
|
||||
struct BSincTag;
|
||||
struct FastBSincTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__)
|
||||
#pragma GCC target("sse")
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits};
|
||||
constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
#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 ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
const __m128 lrlr{_mm_setr_ps(left, right, left, right)};
|
||||
|
||||
ASSUME(IrSize >= MinIrLength);
|
||||
/* This isn't technically correct to test alignment, but it's true for
|
||||
* 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<uintptr_t>(Values)&15))
|
||||
{
|
||||
for(size_t i{0};i < IrSize;i += 2)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(Coeffs[i].data())};
|
||||
__m128 vals{_mm_load_ps(Values[i].data())};
|
||||
vals = MLA4(vals, lrlr, coeffs);
|
||||
_mm_store_ps(Values[i].data(), vals);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__m128 imp0, imp1;
|
||||
__m128 coeffs{_mm_load_ps(Coeffs[0].data())};
|
||||
__m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()))};
|
||||
imp0 = _mm_mul_ps(lrlr, coeffs);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals);
|
||||
size_t td{((IrSize+1)>>1) - 1};
|
||||
size_t i{1};
|
||||
do {
|
||||
coeffs = _mm_load_ps(Coeffs[i+1].data());
|
||||
vals = _mm_load_ps(Values[i].data());
|
||||
imp1 = _mm_mul_ps(lrlr, coeffs);
|
||||
imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_store_ps(Values[i].data(), vals);
|
||||
imp0 = imp1;
|
||||
i += 2;
|
||||
} while(--td);
|
||||
vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(Values[i].data()));
|
||||
imp0 = _mm_movehl_ps(imp0, imp0);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(Values[i].data()), vals);
|
||||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
const size_t aligned_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const __m128 four4{_mm_set1_ps(4.0f)};
|
||||
const __m128 step4{_mm_set1_ps(step)};
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
__m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
|
||||
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
step_count4 = _mm_add_ps(step_count4, four4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after the
|
||||
* last four mixed samples, so the lowest element represents the
|
||||
* next step count to apply.
|
||||
*/
|
||||
step_count = _mm_cvtss_f32(step_count4);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter);
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
|
||||
/* f = fil + pf*phd */
|
||||
const __m128 f4 = MLA4(_mm_load_ps(filter[pi].mCoeffs), pf4,
|
||||
_mm_load_ps(filter[pi].mDeltas));
|
||||
/* r = f*src */
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))};
|
||||
|
||||
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));
|
||||
out_sample = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
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);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
{
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
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};
|
||||
|
||||
do {
|
||||
/* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
|
||||
const __m128 f4 = MLA4(
|
||||
MLA4(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])),
|
||||
pf4, MLA4(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j])));
|
||||
/* r += f*src */
|
||||
r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
|
||||
j += 4;
|
||||
} while(--td);
|
||||
}
|
||||
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));
|
||||
out_sample = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<FastBSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
{
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
const float *RESTRICT fil{filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
do {
|
||||
/* f = fil + pf*phd */
|
||||
const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j]));
|
||||
/* r += f*src */
|
||||
r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
|
||||
j += 4;
|
||||
} while(--td);
|
||||
}
|
||||
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));
|
||||
out_sample = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
void MixHrtf_<SSETag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<SSETag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
}
|
||||
|
||||
template<>
|
||||
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)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
|
||||
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<SSETag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
}
|
||||
90
externals/openal-soft/core/mixer/mixer_sse2.cpp
vendored
Normal file
90
externals/openal-soft/core/mixer/mixer_sse2.cpp
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2014 by Timothy Arceri <t_arceri@yahoo.com.au>.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "defs.h"
|
||||
|
||||
struct SSE2Tag;
|
||||
struct LerpTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__)
|
||||
#pragma GCC target("sse2")
|
||||
#endif
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
|
||||
alignas(16) uint pos_[4], frac_[4];
|
||||
InitPosArrays(frac, increment, frac_, pos_);
|
||||
__m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]),
|
||||
static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))};
|
||||
__m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]),
|
||||
static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))};
|
||||
|
||||
auto dst_iter = dst.begin();
|
||||
for(size_t todo{dst.size()>>2};todo;--todo)
|
||||
{
|
||||
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])};
|
||||
|
||||
/* val1 + (val2-val1)*mu */
|
||||
const __m128 r0{_mm_sub_ps(val2, val1)};
|
||||
const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)};
|
||||
const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))};
|
||||
|
||||
_mm_store_ps(dst_iter, out);
|
||||
dst_iter += 4;
|
||||
|
||||
frac4 = _mm_add_epi32(frac4, increment4);
|
||||
pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits));
|
||||
frac4 = _mm_and_si128(frac4, fracMask4);
|
||||
}
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
{
|
||||
src += static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
}
|
||||
0
externals/openal-soft/core/mixer/mixer_sse3.cpp
vendored
Normal file
0
externals/openal-soft/core/mixer/mixer_sse3.cpp
vendored
Normal file
95
externals/openal-soft/core/mixer/mixer_sse41.cpp
vendored
Normal file
95
externals/openal-soft/core/mixer/mixer_sse41.cpp
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2014 by Timothy Arceri <t_arceri@yahoo.com.au>.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#include <smmintrin.h>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "defs.h"
|
||||
|
||||
struct SSE4Tag;
|
||||
struct LerpTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__)
|
||||
#pragma GCC target("sse4.1")
|
||||
#endif
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
|
||||
alignas(16) uint pos_[4], frac_[4];
|
||||
InitPosArrays(frac, increment, frac_, pos_);
|
||||
__m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]),
|
||||
static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))};
|
||||
__m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]),
|
||||
static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))};
|
||||
|
||||
auto dst_iter = dst.begin();
|
||||
for(size_t todo{dst.size()>>2};todo;--todo)
|
||||
{
|
||||
const int pos0{_mm_extract_epi32(pos4, 0)};
|
||||
const int pos1{_mm_extract_epi32(pos4, 1)};
|
||||
const int pos2{_mm_extract_epi32(pos4, 2)};
|
||||
const int pos3{_mm_extract_epi32(pos4, 3)};
|
||||
const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])};
|
||||
const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
|
||||
|
||||
/* val1 + (val2-val1)*mu */
|
||||
const __m128 r0{_mm_sub_ps(val2, val1)};
|
||||
const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)};
|
||||
const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))};
|
||||
|
||||
_mm_store_ps(dst_iter, out);
|
||||
dst_iter += 4;
|
||||
|
||||
frac4 = _mm_add_epi32(frac4, increment4);
|
||||
pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits));
|
||||
frac4 = _mm_and_si128(frac4, fracMask4);
|
||||
}
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
{
|
||||
/* NOTE: These four elements represent the position *after* the last
|
||||
* four samples, so the lowest element is the next position to
|
||||
* resample.
|
||||
*/
|
||||
src += static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
}
|
||||
12
externals/openal-soft/core/resampler_limits.h
vendored
Normal file
12
externals/openal-soft/core/resampler_limits.h
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef CORE_RESAMPLER_LIMITS_H
|
||||
#define CORE_RESAMPLER_LIMITS_H
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
|
||||
constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1};
|
||||
|
||||
#endif /* CORE_RESAMPLER_LIMITS_H */
|
||||
236
externals/openal-soft/core/rtkit.cpp
vendored
Normal file
236
externals/openal-soft/core/rtkit.cpp
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
/*-*- 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>
|
||||
#ifdef __linux__
|
||||
#include <sys/syscall.h>
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <sys/thr.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace dbus {
|
||||
|
||||
constexpr int TypeString{'s'};
|
||||
constexpr int TypeVariant{'v'};
|
||||
constexpr int TypeInt32{'i'};
|
||||
constexpr int TypeUInt32{'u'};
|
||||
constexpr int TypeInt64{'x'};
|
||||
constexpr int TypeUInt64{'t'};
|
||||
constexpr int TypeInvalid{'\0'};
|
||||
|
||||
struct MessageDeleter {
|
||||
void operator()(DBusMessage *m) { dbus_message_unref(m); }
|
||||
};
|
||||
using MessagePtr = std::unique_ptr<DBusMessage,MessageDeleter>;
|
||||
|
||||
} // namespace dbus
|
||||
|
||||
namespace {
|
||||
|
||||
inline pid_t _gettid()
|
||||
{
|
||||
#ifdef __linux__
|
||||
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||
#elif defined(__FreeBSD__)
|
||||
long pid{};
|
||||
thr_self(&pid);
|
||||
return static_cast<pid_t>(pid);
|
||||
#else
|
||||
#warning gettid not available
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int translate_error(const char *name)
|
||||
{
|
||||
if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0)
|
||||
return -ENOMEM;
|
||||
if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0
|
||||
|| strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0)
|
||||
return -ENOENT;
|
||||
if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0
|
||||
|| strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0)
|
||||
return -EACCES;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval)
|
||||
{
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.DBus.Properties", "Get")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
const char *interfacestr = RTKIT_SERVICE_NAME;
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeString, &interfacestr,
|
||||
dbus::TypeString, &propname,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
int ret{-EBADMSG};
|
||||
DBusMessageIter iter{};
|
||||
dbus_message_iter_init(r.get(), &iter);
|
||||
while(int curtype{dbus_message_iter_get_arg_type(&iter)})
|
||||
{
|
||||
if(curtype == dbus::TypeVariant)
|
||||
{
|
||||
DBusMessageIter subiter{};
|
||||
dbus_message_iter_recurse(&iter, &subiter);
|
||||
|
||||
while((curtype=dbus_message_iter_get_arg_type(&subiter)) != dbus::TypeInvalid)
|
||||
{
|
||||
if(curtype == dbus::TypeInt32)
|
||||
{
|
||||
dbus_int32_t i32{};
|
||||
dbus_message_iter_get_basic(&subiter, &i32);
|
||||
*propval = i32;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if(curtype == dbus::TypeInt64)
|
||||
{
|
||||
dbus_int64_t i64{};
|
||||
dbus_message_iter_get_basic(&subiter, &i64);
|
||||
*propval = i64;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&subiter);
|
||||
}
|
||||
}
|
||||
dbus_message_iter_next(&iter);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *connection)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)};
|
||||
return err < 0 ? err : static_cast<int>(retval);
|
||||
}
|
||||
|
||||
int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)};
|
||||
if(err >= 0) *min_nice_level = static_cast<int>(retval);
|
||||
return err;
|
||||
}
|
||||
|
||||
long long rtkit_get_rttime_usec_max(DBusConnection *connection)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)};
|
||||
return err < 0 ? err : retval;
|
||||
}
|
||||
|
||||
int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
if(thread == 0)
|
||||
return -ENOTSUP;
|
||||
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.RealtimeKit1", "MakeThreadRealtime")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
auto u64 = static_cast<dbus_uint64_t>(thread);
|
||||
auto u32 = static_cast<dbus_uint32_t>(priority);
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeUInt64, &u64,
|
||||
dbus::TypeUInt32, &u32,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
if(thread == 0)
|
||||
return -ENOTSUP;
|
||||
|
||||
dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH,
|
||||
"org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")};
|
||||
if(!m) return -ENOMEM;
|
||||
|
||||
auto u64 = static_cast<dbus_uint64_t>(thread);
|
||||
auto s32 = static_cast<dbus_int32_t>(nice_level);
|
||||
auto ready = dbus_message_append_args(m.get(),
|
||||
dbus::TypeUInt64, &u64,
|
||||
dbus::TypeInt32, &s32,
|
||||
dbus::TypeInvalid);
|
||||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
if(dbus_set_error_from_message(&error.get(), r.get()))
|
||||
return translate_error(error->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
71
externals/openal-soft/core/rtkit.h
vendored
Normal file
71
externals/openal-soft/core/rtkit.h
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8 -*-*/
|
||||
|
||||
#ifndef foortkithfoo
|
||||
#define foortkithfoo
|
||||
|
||||
/***
|
||||
Copyright 2009 Lennart Poettering
|
||||
Copyright 2010 David Henningsson <diwic@ubuntu.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
***/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
|
||||
/* This is the reference implementation for a client for
|
||||
* RealtimeKit. You don't have to use this, but if do, just copy these
|
||||
* sources into your repository */
|
||||
|
||||
#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
|
||||
#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
|
||||
|
||||
/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, {
|
||||
* .sched_priority = priority }). 'thread' needs to be a kernel thread
|
||||
* id as returned by gettid(), not a pthread_t! If 'thread' is 0 the
|
||||
* current thread is used. The returned value is a negative errno
|
||||
* style error code, or 0 on success. */
|
||||
int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority);
|
||||
|
||||
/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread,
|
||||
* nice_level). 'thread' needs to be a kernel thread id as returned by
|
||||
* gettid(), not a pthread_t! If 'thread' is 0 the current thread is
|
||||
* used. The returned value is a negative errno style error code, or 0
|
||||
* on success.*/
|
||||
int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level);
|
||||
|
||||
/* Return the maximum value of realtime priority available. Realtime requests
|
||||
* above this value will fail. A negative value is an errno style error code.
|
||||
*/
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *system_bus);
|
||||
|
||||
/* Retreive the minimum value of nice level available. High prio requests
|
||||
* below this value will fail. The returned value is a negative errno
|
||||
* style error code, or 0 on success.*/
|
||||
int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level);
|
||||
|
||||
/* Return the maximum value of RLIMIT_RTTIME to set before attempting a
|
||||
* realtime request. A negative value is an errno style error code.
|
||||
*/
|
||||
long long rtkit_get_rttime_usec_max(DBusConnection *system_bus);
|
||||
|
||||
#endif
|
||||
539
externals/openal-soft/core/uhjfilter.cpp
vendored
Normal file
539
externals/openal-soft/core/uhjfilter.cpp
vendored
Normal file
@ -0,0 +1,539 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "uhjfilter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alnumeric.h"
|
||||
#include "opthelpers.h"
|
||||
#include "phase_shifter.h"
|
||||
|
||||
|
||||
UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
|
||||
UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const PhaseShifterT<UhjLength256> PShiftLq{};
|
||||
const PhaseShifterT<UhjLength512> PShiftHq{};
|
||||
|
||||
template<size_t N>
|
||||
struct GetPhaseShifter;
|
||||
template<>
|
||||
struct GetPhaseShifter<UhjLength256> { static auto& Get() noexcept { return PShiftLq; } };
|
||||
template<>
|
||||
struct GetPhaseShifter<UhjLength512> { static auto& Get() noexcept { return PShiftHq; } };
|
||||
|
||||
|
||||
constexpr float square(float x) noexcept
|
||||
{ return x*x; }
|
||||
|
||||
/* Filter coefficients for the 'base' all-pass IIR, which applies a frequency-
|
||||
* dependent phase-shift of N degrees. The output of the filter requires a 1-
|
||||
* sample delay.
|
||||
*/
|
||||
constexpr std::array<float,4> Filter1Coeff{{
|
||||
square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f),
|
||||
square(0.9987488452737f)
|
||||
}};
|
||||
/* Filter coefficients for the offset all-pass IIR, which applies a frequency-
|
||||
* dependent phase-shift of N+90 degrees.
|
||||
*/
|
||||
constexpr std::array<float,4> Filter2Coeff{{
|
||||
square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f),
|
||||
square(0.9952884791278f)
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
|
||||
const al::span<const float> src, const bool updateState, float *RESTRICT dst)
|
||||
{
|
||||
auto state = mState;
|
||||
|
||||
auto proc_sample = [&state,coeffs](float x) noexcept -> float
|
||||
{
|
||||
for(size_t i{0};i < 4;++i)
|
||||
{
|
||||
const float y{x*coeffs[i] + state[i].z[0]};
|
||||
state[i].z[0] = state[i].z[1];
|
||||
state[i].z[1] = y*coeffs[i] - x;
|
||||
x = y;
|
||||
}
|
||||
return x;
|
||||
};
|
||||
std::transform(src.begin(), src.end(), dst, proc_sample);
|
||||
if(updateState) LIKELY mState = state;
|
||||
}
|
||||
|
||||
|
||||
/* 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. 3-channel UHJ excludes Q,
|
||||
* while 2-channel excludes Q and T.
|
||||
*
|
||||
* The phase shift is done using a linear FIR filter derived from an FFT'd
|
||||
* impulse with the desired shift.
|
||||
*/
|
||||
|
||||
template<size_t N>
|
||||
void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float*const,3> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
|
||||
|
||||
std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
|
||||
std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
|
||||
std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
|
||||
|
||||
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return -0.3420201f*w + 0.5098604f*x; });
|
||||
PShift.process({mD.data(), SamplesToDo}, mWX.data());
|
||||
|
||||
/* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mD[i] = mD[i] + 0.6554516f*mY[i];
|
||||
|
||||
/* Copy the future samples to the front for next time. */
|
||||
std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin());
|
||||
std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin());
|
||||
std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin());
|
||||
std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin());
|
||||
|
||||
/* Apply a delay to the existing output to align with the input delay. */
|
||||
auto *delayBuffer = mDirectDelay.data();
|
||||
for(float *buffer : {LeftOut, RightOut})
|
||||
{
|
||||
float *distbuf{al::assume_aligned<16>(delayBuffer->data())};
|
||||
++delayBuffer;
|
||||
|
||||
float *inout{al::assume_aligned<16>(buffer)};
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if(SamplesToDo >= sFilterDelay) LIKELY
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end);
|
||||
std::swap_ranges(inout, delay_end, distbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
|
||||
std::rotate(distbuf, delay_start, distbuf + sFilterDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine the direct signal with the produced output. */
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
left[i] += (mS[i] + mD[i]) * 0.5f;
|
||||
/* Right = (S - D)/2.0 */
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] += (mS[i] - mD[i]) * 0.5f;
|
||||
}
|
||||
|
||||
/* This encoding implementation uses two sets of four chained IIR filters to
|
||||
* produce the desired relative phase shift. The first filter chain produces a
|
||||
* phase shift of varying degrees over a wide range of frequencies, while the
|
||||
* second filter chain produces a phase shift 90 degrees ahead of the first
|
||||
* over the same range. Further details are described here:
|
||||
*
|
||||
* https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/
|
||||
*
|
||||
* 2-channel UHJ output requires the use of three filter chains. The S channel
|
||||
* output uses a Filter1 chain on the W and X channel mix, while the D channel
|
||||
* output uses a Filter1 chain on the Y channel plus a Filter2 chain on the W
|
||||
* and X channel mix. This results in the W and X input mix on the D channel
|
||||
* output having the required +90 degree phase shift relative to the other
|
||||
* inputs.
|
||||
*/
|
||||
void UhjEncoderIIR::encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float *const, 3> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
|
||||
[](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; });
|
||||
mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1);
|
||||
mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo];
|
||||
|
||||
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
|
||||
[](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; });
|
||||
mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data());
|
||||
|
||||
/* Apply filter1 to Y and store in mD. */
|
||||
mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1);
|
||||
mD[0] = mDelayY; mDelayY = mD[SamplesToDo];
|
||||
|
||||
/* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mD[i] = mWX[i] + 0.6554516f*mD[i];
|
||||
|
||||
/* Apply the base filter to the existing output to align with the processed
|
||||
* signal.
|
||||
*/
|
||||
mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1);
|
||||
mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo];
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
left[i] = (mS[i] + mD[i])*0.5f + mTemp[i];
|
||||
|
||||
mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1);
|
||||
mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo];
|
||||
|
||||
/* Right = (S - D)/2.0 */
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] = (mS[i] - mD[i])*0.5f + mTemp[i];
|
||||
}
|
||||
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
template<size_t N>
|
||||
void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
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+sInputPadding;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
|
||||
/* T */
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++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+sInputPadding, mT.cbegin(), tmpiter,
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, 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+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, 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];
|
||||
}
|
||||
}
|
||||
|
||||
void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mD[i] = left[i] - right[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. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(),
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput);
|
||||
|
||||
/* Apply filter1 to S and store in mTemp. */
|
||||
mTemp[0] = mDelayS;
|
||||
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
|
||||
|
||||
/* 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*mTemp[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*mTemp[i] - xoutput[i];
|
||||
|
||||
|
||||
/* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput,
|
||||
[](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; });
|
||||
mTemp[0] = mDelayDT;
|
||||
mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayDT = mTemp[samplesToDo];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
|
||||
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = mTemp[i] + 0.186633f*youtput[i];
|
||||
|
||||
|
||||
if(samples.size() > 3)
|
||||
{
|
||||
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
|
||||
|
||||
/* Apply filter1 to Q and store in mTemp. */
|
||||
mTemp[0] = mDelayQ;
|
||||
mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayQ = mTemp[samplesToDo];
|
||||
|
||||
/* Z = 1.023332*Q */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
zoutput[i] = 1.023332f*mTemp[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.
|
||||
*/
|
||||
template<size_t N>
|
||||
void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
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+sInputPadding;++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{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(wtarget == wcurrent || !updateState)
|
||||
{
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = (left[i] - right[i]) * wcurrent;
|
||||
mCurrentWidth = wcurrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
|
||||
float fi{0.0f};
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
{
|
||||
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
|
||||
fi += 1.0f;
|
||||
}
|
||||
for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++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+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, 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+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, 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];
|
||||
}
|
||||
|
||||
void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
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;++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{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(wtarget == wcurrent || !updateState)
|
||||
{
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mD[i] = (left[i] - right[i]) * wcurrent;
|
||||
mCurrentWidth = wcurrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
|
||||
float fi{0.0f};
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
{
|
||||
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
|
||||
fi += 1.0f;
|
||||
}
|
||||
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])};
|
||||
|
||||
/* Apply filter1 to S and store in mTemp. */
|
||||
mTemp[0] = mDelayS;
|
||||
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
|
||||
|
||||
/* Precompute j*D and store in xoutput. */
|
||||
mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput);
|
||||
|
||||
/* W = 0.6098637*S - 0.6896511*j*w*D */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
woutput[i] = 0.6098637f*mTemp[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*mTemp[i] + 0.7626955f*xoutput[i];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
|
||||
/* Apply filter1 to D and store in mTemp. */
|
||||
mTemp[0] = mDelayD;
|
||||
mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayD = mTemp[samplesToDo];
|
||||
|
||||
/* Y = 1.6822415*w*D - 0.2156194*j*S */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i];
|
||||
}
|
||||
|
||||
|
||||
template struct UhjEncoder<UhjLength256>;
|
||||
template struct UhjDecoder<UhjLength256>;
|
||||
template struct UhjStereoDecoder<UhjLength256>;
|
||||
|
||||
template struct UhjEncoder<UhjLength512>;
|
||||
template struct UhjDecoder<UhjLength512>;
|
||||
template struct UhjStereoDecoder<UhjLength512>;
|
||||
234
externals/openal-soft/core/uhjfilter.h
vendored
Normal file
234
externals/openal-soft/core/uhjfilter.h
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
#ifndef CORE_UHJFILTER_H
|
||||
#define CORE_UHJFILTER_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
|
||||
|
||||
static constexpr size_t UhjLength256{256};
|
||||
static constexpr size_t UhjLength512{512};
|
||||
|
||||
enum class UhjQualityType : uint8_t {
|
||||
IIR = 0,
|
||||
FIR256,
|
||||
FIR512,
|
||||
Default = IIR
|
||||
};
|
||||
|
||||
extern UhjQualityType UhjDecodeQuality;
|
||||
extern UhjQualityType UhjEncodeQuality;
|
||||
|
||||
|
||||
struct UhjAllPassFilter {
|
||||
struct AllPassState {
|
||||
/* Last two delayed components for direct form II. */
|
||||
float z[2];
|
||||
};
|
||||
std::array<AllPassState,4> mState;
|
||||
|
||||
void process(const al::span<const float,4> coeffs, const al::span<const float> src,
|
||||
const bool update, float *RESTRICT dst);
|
||||
};
|
||||
|
||||
|
||||
struct UhjEncoderBase {
|
||||
virtual ~UhjEncoderBase() = default;
|
||||
|
||||
virtual size_t getDelay() noexcept = 0;
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
virtual void encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float*const,3> InSamples, const size_t SamplesToDo) = 0;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjEncoder final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{N/2};
|
||||
|
||||
/* Delays and processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
|
||||
/* History and temp storage for the FIR filter. New samples should be
|
||||
* written to index sFilterDelay*2 - 1.
|
||||
*/
|
||||
static constexpr size_t sWXInOffset{sFilterDelay*2 - 1};
|
||||
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mWX{};
|
||||
|
||||
alignas(16) std::array<std::array<float,sFilterDelay>,2> mDirectDelay{};
|
||||
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
|
||||
const size_t SamplesToDo) override;
|
||||
|
||||
DEF_NEWDEL(UhjEncoder)
|
||||
};
|
||||
|
||||
struct UhjEncoderIIR final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{1};
|
||||
|
||||
/* Processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+1> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mWX{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mTemp{};
|
||||
float mDelayWX{}, mDelayY{};
|
||||
|
||||
UhjAllPassFilter mFilter1WX;
|
||||
UhjAllPassFilter mFilter2WX;
|
||||
UhjAllPassFilter mFilter1Y;
|
||||
|
||||
std::array<UhjAllPassFilter,2> mFilter1Direct;
|
||||
std::array<float,2> mDirectDelay{};
|
||||
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
|
||||
const size_t SamplesToDo) override;
|
||||
|
||||
DEF_NEWDEL(UhjEncoderIIR)
|
||||
};
|
||||
|
||||
|
||||
struct DecoderBase {
|
||||
static constexpr size_t sMaxPadding{256};
|
||||
|
||||
/* For 2-channel UHJ, shelf filters should use these LF responses. */
|
||||
static constexpr float sWLFScale{0.661f};
|
||||
static constexpr float sXYLFScale{1.293f};
|
||||
|
||||
virtual ~DecoderBase() = default;
|
||||
|
||||
virtual void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) = 0;
|
||||
|
||||
/**
|
||||
* The width factor for Super Stereo processing. Can be changed in between
|
||||
* calls to decode, with valid values being between 0...0.7.
|
||||
*/
|
||||
float mWidthControl{0.593f};
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjDecoder final : public DecoderBase {
|
||||
/* The number of extra sample frames needed for input. */
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mT{};
|
||||
|
||||
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sInputPadding*2> mTemp{};
|
||||
|
||||
/**
|
||||
* 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 bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjDecoder)
|
||||
};
|
||||
|
||||
struct UhjDecoderIIR final : public DecoderBase {
|
||||
/* FIXME: These IIR decoder filters actually have a 1-sample delay on the
|
||||
* non-filtered components, which is not reflected in the source latency
|
||||
* value. sInputPadding is 0, however, because it doesn't need any extra
|
||||
* input samples.
|
||||
*/
|
||||
static constexpr size_t sInputPadding{0};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mTemp{};
|
||||
float mDelayS{}, mDelayDT{}, mDelayQ{};
|
||||
|
||||
UhjAllPassFilter mFilter1S;
|
||||
UhjAllPassFilter mFilter2DT;
|
||||
UhjAllPassFilter mFilter1DT;
|
||||
UhjAllPassFilter mFilter2S;
|
||||
UhjAllPassFilter mFilter1Q;
|
||||
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjDecoderIIR)
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjStereoDecoder final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
|
||||
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sInputPadding*2> mTemp{};
|
||||
|
||||
/**
|
||||
* 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 decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjStereoDecoder)
|
||||
};
|
||||
|
||||
struct UhjStereoDecoderIIR final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{0};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mTemp{};
|
||||
float mDelayS{}, mDelayD{};
|
||||
|
||||
UhjAllPassFilter mFilter1S;
|
||||
UhjAllPassFilter mFilter2D;
|
||||
UhjAllPassFilter mFilter1D;
|
||||
UhjAllPassFilter mFilter2S;
|
||||
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjStereoDecoderIIR)
|
||||
};
|
||||
|
||||
#endif /* CORE_UHJFILTER_H */
|
||||
37
externals/openal-soft/core/uiddefs.cpp
vendored
Normal file
37
externals/openal-soft/core/uiddefs.cpp
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
#ifndef AL_NO_UID_DEFS
|
||||
|
||||
#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
|
||||
#define INITGUID
|
||||
#include <windows.h>
|
||||
#ifdef HAVE_GUIDDEF_H
|
||||
#include <guiddef.h>
|
||||
#else
|
||||
#include <initguid.h>
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
|
||||
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
|
||||
|
||||
DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16);
|
||||
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
|
||||
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
|
||||
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
|
||||
DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
|
||||
DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
|
||||
|
||||
#ifdef HAVE_WASAPI
|
||||
#include <wtypes.h>
|
||||
#include <devpropdef.h>
|
||||
#include <propkeydef.h>
|
||||
DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
|
||||
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
|
||||
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* AL_NO_UID_DEFS */
|
||||
1304
externals/openal-soft/core/voice.cpp
vendored
Normal file
1304
externals/openal-soft/core/voice.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
280
externals/openal-soft/core/voice.h
vendored
Normal file
280
externals/openal-soft/core/voice.h
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
#ifndef CORE_VOICE_H
|
||||
#define CORE_VOICE_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#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
|
||||
};
|
||||
|
||||
|
||||
constexpr uint MaxPitch{10};
|
||||
|
||||
|
||||
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,MaxAmbiChannels> Current;
|
||||
std::array<float,MaxAmbiChannels> Target;
|
||||
} Gains;
|
||||
};
|
||||
|
||||
|
||||
struct VoiceBufferItem {
|
||||
std::atomic<VoiceBufferItem*> mNext{nullptr};
|
||||
|
||||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
uint mBlockAlign{0u};
|
||||
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<int> 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;
|
||||
|
||||
std::chrono::nanoseconds mStartTime{};
|
||||
|
||||
/* Properties for the attached buffer(s). */
|
||||
FmtChannels mFmtChannels;
|
||||
FmtType mFmtType;
|
||||
uint mFrequency;
|
||||
uint mFrameStep; /**< In steps of the sample type size. */
|
||||
uint mBytesPerBlock; /**< Or for PCM formats, BytesPerFrame. */
|
||||
uint mSamplesPerBlock; /**< Always 1 for PCM formats. */
|
||||
AmbiLayout mAmbiLayout;
|
||||
AmbiScaling mAmbiScaling;
|
||||
uint mAmbiOrder;
|
||||
|
||||
std::unique_ptr<DecoderBase> mDecoder;
|
||||
uint mDecoderPadding{};
|
||||
|
||||
/** Current target parameters used for mixing. */
|
||||
uint mStep{0};
|
||||
|
||||
ResamplerFunc mResampler;
|
||||
|
||||
InterpState mResampleState;
|
||||
|
||||
std::bitset<VoiceFlagCount> mFlags{};
|
||||
uint mNumCallbackBlocks{0};
|
||||
uint mCallbackBlockBase{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 std::chrono::nanoseconds deviceTime,
|
||||
const uint SamplesToDo);
|
||||
|
||||
void prepare(DeviceBase *device);
|
||||
|
||||
static void InitMixer(al::optional<std::string> resampler);
|
||||
|
||||
DEF_NEWDEL(Voice)
|
||||
};
|
||||
|
||||
extern Resampler ResamplerDefault;
|
||||
|
||||
#endif /* CORE_VOICE_H */
|
||||
31
externals/openal-soft/core/voice_change.h
vendored
Normal file
31
externals/openal-soft/core/voice_change.h
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef VOICE_CHANGE_H
|
||||
#define VOICE_CHANGE_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct Voice;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
enum class VChangeState {
|
||||
Reset,
|
||||
Stop,
|
||||
Play,
|
||||
Pause,
|
||||
Restart
|
||||
};
|
||||
struct VoiceChange {
|
||||
Voice *mOldVoice{nullptr};
|
||||
Voice *mVoice{nullptr};
|
||||
uint mSourceID{0};
|
||||
VChangeState mState{};
|
||||
|
||||
std::atomic<VoiceChange*> mNext{nullptr};
|
||||
|
||||
DEF_NEWDEL(VoiceChange)
|
||||
};
|
||||
|
||||
#endif /* VOICE_CHANGE_H */
|
||||
Reference in New Issue
Block a user