mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 08:34:40 +00:00
update openal
This commit is contained in:
parent
62f3b93ff9
commit
6721a6b021
287 changed files with 33851 additions and 27325 deletions
|
|
@ -8,12 +8,13 @@
|
|||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "albit.h"
|
||||
#include "alfstream.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
|
@ -42,21 +43,22 @@ enum class ReaderScope {
|
|||
HFMatrix,
|
||||
};
|
||||
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,2,3)]]
|
||||
#ifdef __MINGW32__
|
||||
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
|
||||
#else
|
||||
[[gnu::format(printf,2,3)]]
|
||||
#endif
|
||||
al::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
|
||||
std::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
|
||||
{
|
||||
al::optional<std::string> ret;
|
||||
std::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)};
|
||||
int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)};
|
||||
if(printed < 0) printed = 0;
|
||||
auto plen = std::min(static_cast<size_t>(printed), str.length());
|
||||
|
||||
/* NOLINTBEGIN(*-array-to-pointer-decay) */
|
||||
std::va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
|
|
@ -68,6 +70,7 @@ al::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
|
|||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
/* NOLINTEND(*-array-to-pointer-decay) */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -77,9 +80,9 @@ al::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
|
|||
AmbDecConf::~AmbDecConf() = default;
|
||||
|
||||
|
||||
al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
||||
std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
||||
{
|
||||
al::ifstream f{fname};
|
||||
std::ifstream f{std::filesystem::u8path(fname)};
|
||||
if(!f.is_open())
|
||||
return std::string("Failed to open file \"")+fname+"\"";
|
||||
|
||||
|
|
@ -111,7 +114,7 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
{
|
||||
if(command == "add_spkr")
|
||||
{
|
||||
if(speaker_pos == NumSpeakers)
|
||||
if(speaker_pos == Speakers.size())
|
||||
return make_error(linenum, "Too many speakers specified");
|
||||
|
||||
AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++];
|
||||
|
|
@ -127,7 +130,7 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix)
|
||||
{
|
||||
auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain;
|
||||
auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix;
|
||||
auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix;
|
||||
auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos;
|
||||
|
||||
if(command == "order_gain")
|
||||
|
|
@ -139,13 +142,13 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
{
|
||||
--toread;
|
||||
istr >> value;
|
||||
if(curgain < al::size(gains))
|
||||
if(curgain < std::size(gains))
|
||||
gains[curgain++] = value;
|
||||
}
|
||||
}
|
||||
else if(command == "add_row")
|
||||
{
|
||||
if(pos == NumSpeakers)
|
||||
if(pos == Speakers.size())
|
||||
return make_error(linenum, "Too many matrix rows specified");
|
||||
|
||||
unsigned int mask{ChanMask};
|
||||
|
|
@ -205,12 +208,13 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
}
|
||||
else if(command == "/dec/speakers")
|
||||
{
|
||||
if(NumSpeakers)
|
||||
if(!Speakers.empty())
|
||||
return make_error(linenum, "Duplicate speakers");
|
||||
istr >> NumSpeakers;
|
||||
if(!NumSpeakers)
|
||||
return make_error(linenum, "Invalid speakers: %zu", NumSpeakers);
|
||||
Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers);
|
||||
size_t numspeakers{};
|
||||
istr >> numspeakers;
|
||||
if(!numspeakers)
|
||||
return make_error(linenum, "Invalid speakers: %zu", numspeakers);
|
||||
Speakers.resize(numspeakers);
|
||||
}
|
||||
else if(command == "/dec/coeff_scale")
|
||||
{
|
||||
|
|
@ -243,22 +247,22 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
}
|
||||
else if(command == "/speakers/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
if(Speakers.empty())
|
||||
return make_error(linenum, "Speakers defined without a count");
|
||||
scope = ReaderScope::Speakers;
|
||||
}
|
||||
else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
if(Speakers.empty())
|
||||
return make_error(linenum, "Matrix defined without a speaker count");
|
||||
if(!ChanMask)
|
||||
return make_error(linenum, "Matrix defined without a channel mask");
|
||||
|
||||
if(!Matrix)
|
||||
if(Matrix.empty())
|
||||
{
|
||||
Matrix = std::make_unique<CoeffArray[]>(NumSpeakers * FreqBands);
|
||||
LFMatrix = Matrix.get();
|
||||
HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1);
|
||||
Matrix.resize(Speakers.size() * FreqBands);
|
||||
LFMatrix = al::span{Matrix}.first(Speakers.size());
|
||||
HFMatrix = al::span{Matrix}.subspan(Speakers.size()*(FreqBands-1));
|
||||
}
|
||||
|
||||
if(FreqBands == 1)
|
||||
|
|
@ -285,13 +289,13 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
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))
|
||||
if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size()
|
||||
|| (FreqBands == 2 && lfmatrix_pos < Speakers.size()))
|
||||
return make_error(linenum, "Incomplete decoder definition");
|
||||
if(CoeffScale == AmbDecScale::Unset)
|
||||
return make_error(linenum, "No coefficient scaling defined");
|
||||
|
||||
return al::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
return make_error(linenum, "Unexpected command: %s", command.c_str());
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "core/ambidefs.h"
|
||||
|
||||
/* Helpers to read .ambdec configuration files. */
|
||||
|
|
@ -34,22 +36,21 @@ struct AmbDecConf {
|
|||
float Elevation{0.0f};
|
||||
std::string Connection;
|
||||
};
|
||||
size_t NumSpeakers{0};
|
||||
std::unique_ptr<SpeakerConf[]> Speakers;
|
||||
std::vector<SpeakerConf> Speakers;
|
||||
|
||||
using CoeffArray = std::array<float,MaxAmbiChannels>;
|
||||
std::unique_ptr<CoeffArray[]> Matrix;
|
||||
std::vector<CoeffArray> Matrix;
|
||||
|
||||
/* Unused when FreqBands == 1 */
|
||||
float LFOrderGain[MaxAmbiOrder+1]{};
|
||||
CoeffArray *LFMatrix;
|
||||
std::array<float,MaxAmbiOrder+1> LFOrderGain{};
|
||||
al::span<CoeffArray> LFMatrix;
|
||||
|
||||
float HFOrderGain[MaxAmbiOrder+1]{};
|
||||
CoeffArray *HFMatrix;
|
||||
std::array<float,MaxAmbiOrder+1> HFOrderGain{};
|
||||
al::span<CoeffArray> HFMatrix;
|
||||
|
||||
~AmbDecConf();
|
||||
|
||||
al::optional<std::string> load(const char *fname) noexcept;
|
||||
std::optional<std::string> load(const char *fname) noexcept;
|
||||
};
|
||||
|
||||
#endif /* CORE_AMBDEC_H */
|
||||
|
|
|
|||
|
|
@ -21,25 +21,25 @@ constexpr auto inv_sqrt3f = static_cast<float>(1.0/al::numbers::sqrt3);
|
|||
* 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 */
|
||||
}};
|
||||
constexpr std::array HFScales{
|
||||
std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f},
|
||||
std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f},
|
||||
std::array{2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f},
|
||||
std::array{2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f},
|
||||
/*std::array{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 */
|
||||
}};
|
||||
constexpr std::array HFScales2D{
|
||||
std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f},
|
||||
std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f},
|
||||
std::array{1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f},
|
||||
std::array{1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f},
|
||||
/*std::array{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
|
||||
|
|
@ -49,17 +49,17 @@ constexpr std::array<std::array<float,MaxAmbiOrder+1>,MaxAmbiOrder+1> HFScales2D
|
|||
* 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{{
|
||||
constexpr std::array FirstOrderDecoder{
|
||||
std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f},
|
||||
std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f},
|
||||
};
|
||||
constexpr std::array FirstOrderEncoder{
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
|
|
@ -68,25 +68,25 @@ 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),
|
||||
}};
|
||||
};
|
||||
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{{
|
||||
constexpr std::array FirstOrder2DDecoder{
|
||||
std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f},
|
||||
std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f},
|
||||
std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f},
|
||||
std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f},
|
||||
};
|
||||
constexpr std::array 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");
|
||||
|
||||
|
||||
|
|
@ -94,21 +94,21 @@ static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-o
|
|||
* 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{{
|
||||
constexpr std::array SecondOrderDecoder{
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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 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),
|
||||
|
|
@ -121,29 +121,29 @@ 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),
|
||||
}};
|
||||
};
|
||||
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{{
|
||||
constexpr std::array SecondOrder2DDecoder{
|
||||
std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f},
|
||||
std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f},
|
||||
std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f},
|
||||
std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f},
|
||||
std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f},
|
||||
std::array{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 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");
|
||||
|
||||
|
|
@ -152,29 +152,29 @@ static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(),
|
|||
* 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{{
|
||||
constexpr std::array ThirdOrderDecoder{
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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 ThirdOrderEncoder{
|
||||
CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f),
|
||||
|
|
@ -195,24 +195,24 @@ constexpr std::array<AmbiChannelFloatArray,20> ThirdOrderEncoder{{
|
|||
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{{
|
||||
constexpr std::array ThirdOrder2DDecoder{
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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 ThirdOrder2DEncoder{
|
||||
CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f),
|
||||
|
|
@ -221,7 +221,7 @@ constexpr std::array<AmbiChannelFloatArray,8> ThirdOrder2DEncoder{{
|
|||
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");
|
||||
|
||||
|
||||
|
|
@ -230,19 +230,19 @@ static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-o
|
|||
* 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{{
|
||||
constexpr std::array FourthOrder2DDecoder{
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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},
|
||||
std::array{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 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),
|
||||
|
|
@ -253,12 +253,12 @@ constexpr std::array<AmbiChannelFloatArray,10> FourthOrder2DEncoder{{
|
|||
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,
|
||||
constexpr auto CalcAmbiUpsampler(const std::array<std::array<float,N>,M> &decoder,
|
||||
const std::array<AmbiChannelFloatArray,M> &encoder)
|
||||
{
|
||||
std::array<AmbiChannelFloatArray,N> res{};
|
||||
|
|
@ -279,13 +279,13 @@ auto CalcAmbiUpsampler(const std::array<std::array<float,N>,M> &decoder,
|
|||
|
||||
} // 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)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)};
|
||||
const std::array<std::array<float,MaxAmbiChannels>,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiOrder+1> AmbiScale::GetHFOrderScales(const uint src_order,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
#define CORE_AMBIDEFS_H
|
||||
|
||||
#include <array>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "alnumbers.h"
|
||||
|
||||
|
|
@ -14,104 +14,88 @@ using uint = unsigned int;
|
|||
* 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
|
||||
inline constexpr auto MaxAmbiOrder = std::uint8_t{3};
|
||||
inline constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t
|
||||
{ return (order+1) * (order+1); }
|
||||
constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)};
|
||||
inline constexpr auto MaxAmbiChannels = size_t{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};
|
||||
inline constexpr uint Ambi0OrderMask{0x00000001};
|
||||
inline constexpr uint Ambi1OrderMask{0x0000000f};
|
||||
inline constexpr uint Ambi2OrderMask{0x000001ff};
|
||||
inline constexpr uint Ambi3OrderMask{0x0000ffff};
|
||||
inline 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};
|
||||
inline 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
|
||||
inline constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t
|
||||
{ return order*2 + 1; }
|
||||
constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)};
|
||||
inline constexpr auto MaxAmbi2DChannels = std::size_t{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;
|
||||
}
|
||||
static inline constexpr std::array<float,MaxAmbiChannels> FromN3D{{
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
|
||||
}};
|
||||
static inline constexpr std::array<float,MaxAmbiChannels> FromSN3D{{
|
||||
1.000000000f, /* ACN 0, sqrt(1) */
|
||||
1.732050808f, /* ACN 1, sqrt(3) */
|
||||
1.732050808f, /* ACN 2, sqrt(3) */
|
||||
1.732050808f, /* ACN 3, sqrt(3) */
|
||||
2.236067978f, /* ACN 4, sqrt(5) */
|
||||
2.236067978f, /* ACN 5, sqrt(5) */
|
||||
2.236067978f, /* ACN 6, sqrt(5) */
|
||||
2.236067978f, /* ACN 7, sqrt(5) */
|
||||
2.236067978f, /* ACN 8, sqrt(5) */
|
||||
2.645751311f, /* ACN 9, sqrt(7) */
|
||||
2.645751311f, /* ACN 10, sqrt(7) */
|
||||
2.645751311f, /* ACN 11, sqrt(7) */
|
||||
2.645751311f, /* ACN 12, sqrt(7) */
|
||||
2.645751311f, /* ACN 13, sqrt(7) */
|
||||
2.645751311f, /* ACN 14, sqrt(7) */
|
||||
2.645751311f, /* ACN 15, sqrt(7) */
|
||||
}};
|
||||
static inline constexpr std::array<float,MaxAmbiChannels> FromFuMa{{
|
||||
1.414213562f, /* ACN 0 (W), sqrt(2) */
|
||||
1.732050808f, /* ACN 1 (Y), sqrt(3) */
|
||||
1.732050808f, /* ACN 2 (Z), sqrt(3) */
|
||||
1.732050808f, /* ACN 3 (X), sqrt(3) */
|
||||
1.936491673f, /* ACN 4 (V), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 5 (T), sqrt(15)/2 */
|
||||
2.236067978f, /* ACN 6 (R), sqrt(5) */
|
||||
1.936491673f, /* ACN 7 (S), sqrt(15)/2 */
|
||||
1.936491673f, /* ACN 8 (U), sqrt(15)/2 */
|
||||
2.091650066f, /* ACN 9 (Q), sqrt(35/8) */
|
||||
1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
|
||||
2.231093404f, /* ACN 11 (M), sqrt(224/45) */
|
||||
2.645751311f, /* ACN 12 (K), sqrt(7) */
|
||||
2.231093404f, /* ACN 13 (L), sqrt(224/45) */
|
||||
1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
|
||||
2.091650066f, /* ACN 15 (P), sqrt(35/8) */
|
||||
}};
|
||||
static inline constexpr std::array<float,MaxAmbiChannels> FromUHJ{{
|
||||
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,
|
||||
}};
|
||||
|
||||
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
|
||||
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint src_order,
|
||||
|
|
@ -127,72 +111,49 @@ struct AmbiScale {
|
|||
};
|
||||
|
||||
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 inline constexpr std::array<std::uint8_t,MaxAmbiChannels> FromFuMa{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
2, /* Z */
|
||||
6, /* R */
|
||||
7, /* S */
|
||||
5, /* T */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
12, /* K */
|
||||
13, /* L */
|
||||
11, /* M */
|
||||
14, /* N */
|
||||
10, /* O */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
static inline constexpr std::array<std::uint8_t,MaxAmbi2DChannels> FromFuMa2D{{
|
||||
0, /* W */
|
||||
3, /* X */
|
||||
1, /* Y */
|
||||
8, /* U */
|
||||
4, /* V */
|
||||
15, /* P */
|
||||
9, /* Q */
|
||||
}};
|
||||
|
||||
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 inline constexpr std::array<std::uint8_t,MaxAmbiChannels> FromACN{{
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15
|
||||
}};
|
||||
static inline constexpr std::array<std::uint8_t,MaxAmbi2DChannels> FromACN2D{{
|
||||
0, 1,3, 4,8, 9,15
|
||||
}};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static inline constexpr std::array<std::uint8_t,MaxAmbiChannels> OrderFromChannel{{
|
||||
0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
|
||||
}};
|
||||
static inline constexpr std::array<std::uint8_t,MaxAmbi2DChannels> OrderFrom2DChannel{{
|
||||
0, 1,1, 2,2, 3,3,
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
#ifndef CORE_EVENT_H
|
||||
#define CORE_EVENT_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct EffectState;
|
||||
|
|
@ -8,48 +13,53 @@ 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()
|
||||
enum class AsyncEnableBits : std::uint8_t {
|
||||
SourceState,
|
||||
BufferCompleted,
|
||||
Disconnected,
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
enum class AsyncSrcState : std::uint8_t {
|
||||
Reset,
|
||||
Stop,
|
||||
Play,
|
||||
Pause
|
||||
};
|
||||
|
||||
using AsyncKillThread = std::monostate;
|
||||
|
||||
struct AsyncSourceStateEvent {
|
||||
uint mId;
|
||||
AsyncSrcState mState;
|
||||
};
|
||||
|
||||
struct AsyncBufferCompleteEvent {
|
||||
uint mId;
|
||||
uint mCount;
|
||||
};
|
||||
|
||||
struct AsyncDisconnectEvent {
|
||||
std::string msg;
|
||||
};
|
||||
|
||||
struct AsyncEffectReleaseEvent {
|
||||
EffectState *mEffectState;
|
||||
};
|
||||
|
||||
using AsyncEvent = std::variant<AsyncKillThread,
|
||||
AsyncSourceStateEvent,
|
||||
AsyncBufferCompleteEvent,
|
||||
AsyncEffectReleaseEvent,
|
||||
AsyncDisconnectEvent>;
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
auto &InitAsyncEvent(std::byte *evtbuf, Args&& ...args)
|
||||
{
|
||||
auto *evt = al::construct_at(reinterpret_cast<AsyncEvent*>(evtbuf), std::in_place_type<T>,
|
||||
std::forward<Args>(args)...);
|
||||
return std::get<T>(*evt);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -6,114 +6,122 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "bufferline.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "flexarray.h"
|
||||
#include "front_stablizer.h"
|
||||
#include "mixer.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
|
||||
template<typename... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
} // namespace
|
||||
|
||||
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}
|
||||
: mStablizer{std::move(stablizer)}
|
||||
{
|
||||
if(!mDualBand)
|
||||
if(coeffslf.empty())
|
||||
{
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
auto &decoder = mChannelDec.emplace<std::vector<ChannelDecoderSingle>>(inchans);
|
||||
for(size_t j{0};j < decoder.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Single};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains.begin(),
|
||||
[j](const ChannelDec &incoeffs) { return incoeffs[j]; });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelDec[0].mXOver.init(xover_f0norm);
|
||||
for(size_t j{1};j < mChannelDec.size();++j)
|
||||
mChannelDec[j].mXOver = mChannelDec[0].mXOver;
|
||||
auto &decoder = mChannelDec.emplace<std::vector<ChannelDecoderDual>>(inchans);
|
||||
decoder[0].mXOver.init(xover_f0norm);
|
||||
for(size_t j{1};j < decoder.size();++j)
|
||||
decoder[j].mXOver = decoder[0].mXOver;
|
||||
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
for(size_t j{0};j < decoder.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains[sHFBand].begin(),
|
||||
[j](const ChannelDec &incoeffs) { return incoeffs[j]; });
|
||||
|
||||
outcoeffs = mChannelDec[j].mGains.Dual[sLFBand];
|
||||
for(const ChannelDec &incoeffs : coeffslf)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
std::transform(coeffslf.cbegin(), coeffslf.cend(), decoder[j].mGains[sLFBand].begin(),
|
||||
[j](const ChannelDec &incoeffs) { return incoeffs[j]; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo)
|
||||
const al::span<const FloatBufferLine> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
if(mDualBand)
|
||||
auto decode_dualband = [=](std::vector<ChannelDecoderDual> &decoder)
|
||||
{
|
||||
const al::span<float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
|
||||
const al::span<float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
|
||||
for(auto &chandec : mChannelDec)
|
||||
auto input = InSamples.cbegin();
|
||||
const auto hfSamples = al::span<float>{mSamples[sHFBand]}.first(SamplesToDo);
|
||||
const auto lfSamples = al::span<float>{mSamples[sLFBand]}.first(SamplesToDo);
|
||||
for(auto &chandec : decoder)
|
||||
{
|
||||
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;
|
||||
chandec.mXOver.process(al::span{*input++}.first(SamplesToDo), hfSamples, lfSamples);
|
||||
MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand],0,0);
|
||||
MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand],0,0);
|
||||
}
|
||||
}
|
||||
else
|
||||
};
|
||||
auto decode_singleband = [=](std::vector<ChannelDecoderSingle> &decoder)
|
||||
{
|
||||
for(auto &chandec : mChannelDec)
|
||||
auto input = InSamples.cbegin();
|
||||
for(auto &chandec : decoder)
|
||||
{
|
||||
MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
|
||||
chandec.mGains.Single, 0, 0);
|
||||
++InSamples;
|
||||
MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains,
|
||||
chandec.mGains, 0, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::visit(overloaded{decode_dualband, decode_singleband}, mChannelDec);
|
||||
}
|
||||
|
||||
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)
|
||||
const al::span<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);
|
||||
const auto leftout = al::span<float>{OutBuffer[lidx]}.first(SamplesToDo);
|
||||
const auto rightout = al::span<float>{OutBuffer[ridx]}.first(SamplesToDo);
|
||||
const al::span<float> mid{al::assume_aligned<16>(mStablizer->MidDirect.data()), SamplesToDo};
|
||||
const al::span<float> side{al::assume_aligned<16>(mStablizer->Side.data()), SamplesToDo};
|
||||
std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{});
|
||||
std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{});
|
||||
std::fill_n(leftout.begin(), leftout.size(), 0.0f);
|
||||
std::fill_n(rightout.begin(), rightout.size(), 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];
|
||||
side[i] += leftout[i] - rightout[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; });
|
||||
const auto tmpsamples = al::span{mStablizer->Temp}.first(SamplesToDo);
|
||||
std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), tmpsamples.begin(),
|
||||
std::plus{});
|
||||
|
||||
mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(),
|
||||
mStablizer->MidLF.data());
|
||||
mStablizer->MidFilter.process(tmpsamples, mStablizer->MidHF, mStablizer->MidLF);
|
||||
|
||||
/* 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
|
||||
|
|
@ -126,9 +134,9 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
* and substitute the direct mid signal and direct+decoded side signal.
|
||||
*/
|
||||
if(i == lidx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({mid, SamplesToDo});
|
||||
mStablizer->ChannelFilters[i].processAllPass(mid);
|
||||
else if(i == ridx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo});
|
||||
mStablizer->ChannelFilters[i].processAllPass(side);
|
||||
else
|
||||
mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo});
|
||||
}
|
||||
|
|
@ -142,6 +150,7 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
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))};
|
||||
const auto centerout = al::span<float>{OutBuffer[cidx]}.first(SamplesToDo);
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
{
|
||||
/* Add the direct mid signal to the processed mid signal so it can be
|
||||
|
|
@ -154,9 +163,9 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
/* 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;
|
||||
leftout[i] = (m + s) * 0.5f;
|
||||
rightout[i] = (m - s) * 0.5f;
|
||||
centerout[i] += c * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@
|
|||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct FrontStablizer;
|
||||
#include "front_stablizer.h"
|
||||
|
||||
|
||||
using ChannelDec = std::array<float,MaxAmbiChannels>;
|
||||
|
|
@ -23,49 +22,40 @@ class BFormatDec {
|
|||
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;
|
||||
struct ChannelDecoderSingle {
|
||||
std::array<float,MaxOutputChannels> mGains{};
|
||||
};
|
||||
|
||||
alignas(16) std::array<FloatBufferLine,2> mSamples;
|
||||
struct ChannelDecoderDual {
|
||||
BandSplitter mXOver;
|
||||
std::array<std::array<float,MaxOutputChannels>,sNumBands> mGains{};
|
||||
};
|
||||
|
||||
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;
|
||||
std::variant<std::vector<ChannelDecoderSingle>,std::vector<ChannelDecoderDual>> 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; }
|
||||
[[nodiscard]] auto hasStablizer() const noexcept -> bool { 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);
|
||||
void process(const al::span<FloatBufferLine> OutBuffer,
|
||||
const al::span<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);
|
||||
const al::span<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 */
|
||||
|
|
|
|||
|
|
@ -26,72 +26,75 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "alspan.h"
|
||||
#include "bs2b.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/* Set up all data. */
|
||||
static void init(struct bs2b *bs2b)
|
||||
void init(Bs2b::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 */
|
||||
case Bs2b::LowCLevel: /* 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 */
|
||||
case Bs2b::MiddleCLevel: /* 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) */
|
||||
case Bs2b::HighCLevel: /* 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 */
|
||||
case Bs2b::LowECLevel: /* 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 */
|
||||
case Bs2b::MiddleECLevel: /* 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;
|
||||
case Bs2b::HighECLevel: /* High easy crossfeed level */
|
||||
default:
|
||||
bs2b->level = Bs2b::HighECLevel;
|
||||
|
||||
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);
|
||||
float 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));
|
||||
float 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;
|
||||
|
||||
|
|
@ -99,85 +102,88 @@ static void init(struct bs2b *bs2b)
|
|||
bs2b->b1_hi = x;
|
||||
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
|
||||
bs2b->a1_hi = -x * g;
|
||||
} /* init */
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* Exported functions.
|
||||
* See descriptions in "bs2b.h"
|
||||
*/
|
||||
namespace Bs2b {
|
||||
|
||||
void bs2b_set_params(struct bs2b *bs2b, int level, int srate)
|
||||
void bs2b::set_params(int level_, int srate_)
|
||||
{
|
||||
if(srate <= 0) srate = 1;
|
||||
if(srate_ < 1)
|
||||
throw std::runtime_error{"BS2B srate < 1"};
|
||||
|
||||
bs2b->level = level;
|
||||
bs2b->srate = srate;
|
||||
init(bs2b);
|
||||
} /* bs2b_set_params */
|
||||
level = level_;
|
||||
srate = srate_;
|
||||
init(this);
|
||||
}
|
||||
|
||||
int bs2b_get_level(struct bs2b *bs2b)
|
||||
void bs2b::clear()
|
||||
{
|
||||
return bs2b->level;
|
||||
} /* bs2b_get_level */
|
||||
history.fill(bs2b::t_last_sample{});
|
||||
}
|
||||
|
||||
int bs2b_get_srate(struct bs2b *bs2b)
|
||||
void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo)
|
||||
{
|
||||
return bs2b->srate;
|
||||
} /* bs2b_get_srate */
|
||||
const float a0lo{a0_lo};
|
||||
const float b1lo{b1_lo};
|
||||
const float a0hi{a0_hi};
|
||||
const float a1hi{a1_hi};
|
||||
const float b1hi{b1_hi};
|
||||
std::array<std::array<float,2>,128> samples{};
|
||||
al::span<float> lsamples{Left, SamplesToDo};
|
||||
al::span<float> rsamples{Right, SamplesToDo};
|
||||
|
||||
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;)
|
||||
while(!lsamples.empty())
|
||||
{
|
||||
const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
|
||||
const size_t todo{std::min(samples.size(), lsamples.size())};
|
||||
|
||||
/* 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];
|
||||
float z_lo{history[0].lo};
|
||||
float z_hi{history[0].hi};
|
||||
std::transform(lsamples.cbegin(), lsamples.cbegin()+ptrdiff_t(todo), samples.begin(),
|
||||
[a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) -> std::array<float,2>
|
||||
{
|
||||
float y0{a0hi*x + z_hi};
|
||||
z_hi = a1hi*x + b1hi*y0;
|
||||
|
||||
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;
|
||||
float y1{a0lo*x + z_lo};
|
||||
z_lo = b1lo*y1;
|
||||
|
||||
return {y0, y1};
|
||||
});
|
||||
history[0].lo = z_lo;
|
||||
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];
|
||||
z_lo = history[1].lo;
|
||||
z_hi = history[1].hi;
|
||||
std::transform(rsamples.cbegin(), rsamples.cbegin()+ptrdiff_t(todo), samples.begin(),
|
||||
samples.begin(),
|
||||
[a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array<float,2> out) -> std::array<float,2>
|
||||
{
|
||||
float y0{a0lo*x + z_lo};
|
||||
z_lo = b1lo*y0;
|
||||
|
||||
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;
|
||||
float y1{a0hi*x + z_hi};
|
||||
z_hi = a1hi*x + b1hi*y1;
|
||||
|
||||
/* 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];
|
||||
return {out[0]+y0, out[1]+y1};
|
||||
});
|
||||
history[1].lo = z_lo;
|
||||
history[1].hi = z_hi;
|
||||
|
||||
base += todo;
|
||||
auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(),
|
||||
[](const std::array<float,2> &in) { return in[0]; });
|
||||
lsamples = {iter, lsamples.end()};
|
||||
|
||||
iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(),
|
||||
[](const std::array<float,2> &in) { return in[1]; });
|
||||
rsamples = {iter, rsamples.end()};
|
||||
}
|
||||
} /* bs2b_cross_feed */
|
||||
}
|
||||
|
||||
} // namespace Bs2b
|
||||
|
|
|
|||
|
|
@ -24,66 +24,66 @@
|
|||
#ifndef CORE_BS2B_H
|
||||
#define CORE_BS2B_H
|
||||
|
||||
#include "almalloc.h"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
/* Number of crossfeed levels */
|
||||
#define BS2B_CLEVELS 3
|
||||
namespace Bs2b {
|
||||
|
||||
/* Normal crossfeed levels */
|
||||
#define BS2B_HIGH_CLEVEL 3
|
||||
#define BS2B_MIDDLE_CLEVEL 2
|
||||
#define BS2B_LOW_CLEVEL 1
|
||||
enum {
|
||||
/* Normal crossfeed levels */
|
||||
LowCLevel = 1,
|
||||
MiddleCLevel = 2,
|
||||
HighCLevel = 3,
|
||||
|
||||
/* 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
|
||||
/* Easy crossfeed levels */
|
||||
LowECLevel = 4,
|
||||
MiddleECLevel = 5,
|
||||
HighECLevel = 6,
|
||||
|
||||
/* Default crossfeed levels */
|
||||
#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL
|
||||
/* Default sample rate (Hz) */
|
||||
#define BS2B_DEFAULT_SRATE 44100
|
||||
DefaultCLevel = HighECLevel
|
||||
};
|
||||
|
||||
struct bs2b {
|
||||
int level; /* Crossfeed level */
|
||||
int srate; /* Sample rate (Hz) */
|
||||
int level{}; /* Crossfeed level */
|
||||
int srate{}; /* Sample rate (Hz) */
|
||||
|
||||
/* Lowpass IIR filter coefficients */
|
||||
float a0_lo;
|
||||
float b1_lo;
|
||||
float a0_lo{};
|
||||
float b1_lo{};
|
||||
|
||||
/* Highboost IIR filter coefficients */
|
||||
float a0_hi;
|
||||
float a1_hi;
|
||||
float b1_hi;
|
||||
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];
|
||||
float lo{};
|
||||
float hi{};
|
||||
};
|
||||
std::array<t_last_sample,2> history{};
|
||||
|
||||
DEF_NEWDEL(bs2b)
|
||||
/* Clear buffers and set new coefficients with new crossfeed level and
|
||||
* sample rate values.
|
||||
* level - crossfeed level of *Level enum values.
|
||||
* srate - sample rate by Hz.
|
||||
*/
|
||||
void set_params(int level, int srate);
|
||||
|
||||
/* Return current crossfeed level value */
|
||||
[[nodiscard]] auto get_level() const noexcept -> int { return level; }
|
||||
|
||||
/* Return current sample rate value */
|
||||
[[nodiscard]] auto get_srate() const noexcept -> int { return srate; }
|
||||
|
||||
/* Clear buffer */
|
||||
void clear();
|
||||
|
||||
void cross_feed(float *Left, float *Right, std::size_t SamplesToDo);
|
||||
};
|
||||
|
||||
/* 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);
|
||||
} // namespace Bs2b
|
||||
|
||||
#endif /* CORE_BS2B_H */
|
||||
|
|
|
|||
|
|
@ -5,18 +5,63 @@
|
|||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "bsinc_defs.h"
|
||||
#include "resampler_limits.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
#if __cpp_lib_math_special_functions >= 201603L
|
||||
using std::cyl_bessel_i;
|
||||
|
||||
#else
|
||||
|
||||
/* 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
|
||||
*
|
||||
* This implementation only handles nu = 0, and isn't the most precise (it
|
||||
* starts with the largest value and accumulates successively smaller values,
|
||||
* compounding the rounding and precision error), but it's good enough.
|
||||
*/
|
||||
template<typename T, typename U>
|
||||
U cyl_bessel_i(T nu, U x)
|
||||
{
|
||||
if(nu != T{0})
|
||||
throw std::runtime_error{"cyl_bessel_i: nu != 0"};
|
||||
|
||||
/* Start at k=1 since k=0 is trivial. */
|
||||
const double x2{x/2.0};
|
||||
double term{1.0};
|
||||
double sum{1.0};
|
||||
int k{1};
|
||||
|
||||
/* Let the integration converge until the term of the sum is no longer
|
||||
* significant.
|
||||
*/
|
||||
double last_sum{};
|
||||
do {
|
||||
const double y{x2 / k};
|
||||
++k;
|
||||
last_sum = sum;
|
||||
term *= y * y;
|
||||
sum += term;
|
||||
} while(sum != last_sum);
|
||||
return static_cast<U>(sum);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This is the normalized cardinal sine (sinc) function.
|
||||
*
|
||||
|
|
@ -31,35 +76,6 @@ constexpr double Sinc(const double x)
|
|||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
* 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].
|
||||
*
|
||||
|
|
@ -78,7 +94,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_
|
|||
{
|
||||
if(!(k >= -1.0 && k <= 1.0))
|
||||
return 0.0;
|
||||
return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
|
||||
return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
|
||||
}
|
||||
|
||||
/* Calculates the (normalized frequency) transition width of the Kaiser window.
|
||||
|
|
@ -97,7 +113,7 @@ constexpr double CalcKaiserBeta(const double rejection)
|
|||
{
|
||||
if(rejection > 50.0)
|
||||
return 0.1102 * (rejection-8.7);
|
||||
else if(rejection >= 21.0)
|
||||
if(rejection >= 21.0)
|
||||
return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0));
|
||||
return 0.0;
|
||||
}
|
||||
|
|
@ -107,24 +123,18 @@ struct BSincHeader {
|
|||
double width{};
|
||||
double beta{};
|
||||
double scaleBase{};
|
||||
double scaleRange{};
|
||||
double besseli_0_beta{};
|
||||
|
||||
uint a[BSincScaleCount]{};
|
||||
std::array<uint,BSincScaleCount> a{};
|
||||
uint total_size{};
|
||||
|
||||
constexpr BSincHeader(uint Rejection, uint Order) noexcept
|
||||
: width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)}
|
||||
, scaleBase{width / 2.0}
|
||||
{
|
||||
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 double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})};
|
||||
const uint a_{std::min(static_cast<uint>(num_points / 2.0 / scale), num_points)};
|
||||
const uint m{2 * a_};
|
||||
|
||||
|
|
@ -142,37 +152,19 @@ 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 constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~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);
|
||||
|
||||
using filter_type = std::array<std::array<double,BSincPointsMax>,BSincPhaseCount>;
|
||||
auto filter = std::vector<filter_type>(BSincScaleCount);
|
||||
|
||||
const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)};
|
||||
|
||||
/* Calculate the Kaiser-windowed Sinc filter coefficients for each
|
||||
* scale and phase index.
|
||||
|
|
@ -181,22 +173,19 @@ struct BSincFilterArray {
|
|||
{
|
||||
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 scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{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)
|
||||
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 *
|
||||
filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff *
|
||||
Sinc(cutoff*x);
|
||||
}
|
||||
}
|
||||
|
|
@ -219,59 +208,82 @@ struct BSincFilterArray {
|
|||
/* 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)
|
||||
if(pi < BSincPhaseCount-1)
|
||||
{
|
||||
const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]};
|
||||
mTable[idx++] = static_cast<float>(phDelta);
|
||||
for(size_t i{0};i < m;++i)
|
||||
{
|
||||
const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]};
|
||||
mTable[idx++] = static_cast<float>(phDelta);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The delta target for the last phase index is the first
|
||||
* phase index with the coefficients offset by one. The
|
||||
* first delta targets 0, as it represents a coefficient
|
||||
* for a sample that won't be part of the filter.
|
||||
*/
|
||||
mTable[idx++] = static_cast<float>(0.0 - filter[si][pi][o]);
|
||||
for(size_t i{1};i < m;++i)
|
||||
{
|
||||
const double phDelta{filter[si][0][o+i-1] - 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.
|
||||
|
||||
/* Now write out each phase index's scale and phase+scale deltas,
|
||||
* to complete the bilinear equation for the combination of phase
|
||||
* and scale.
|
||||
*/
|
||||
if(si == BSincScaleCount-1)
|
||||
if(si < BSincScaleCount-1)
|
||||
{
|
||||
for(size_t pi{0};pi < BSincPhaseCount;++pi)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if(pi < BSincPhaseCount-1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mTable[idx++] = static_cast<float>((0.0 - filter[si+1][pi][o]) -
|
||||
(0.0 - filter[si][pi][o]));
|
||||
for(size_t i{1};i < m;++i)
|
||||
{
|
||||
const double spDelta{(filter[si+1][0][o+i-1] - filter[si+1][pi][o+i]) -
|
||||
(filter[si][0][o+i-1] - filter[si][pi][o+i])};
|
||||
mTable[idx++] = static_cast<float>(spDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The last scale index doesn't have scale-related deltas. */
|
||||
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(); }
|
||||
[[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; }
|
||||
[[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; }
|
||||
};
|
||||
|
||||
#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)
|
||||
|
|
@ -279,7 +291,7 @@ 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);
|
||||
ret.scaleRange = static_cast<float>(1.0 / (1.0 - hdr.scaleBase));
|
||||
for(size_t i{0};i < BSincScaleCount;++i)
|
||||
ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u;
|
||||
ret.filterOffset[0] = 0;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
#ifndef CORE_BSINC_TABLES_H
|
||||
#define CORE_BSINC_TABLES_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "bsinc_defs.h"
|
||||
|
||||
|
||||
struct BSincTable {
|
||||
float scaleBase, scaleRange;
|
||||
unsigned int m[BSincScaleCount];
|
||||
unsigned int filterOffset[BSincScaleCount];
|
||||
const float *Tab;
|
||||
std::array<unsigned int,BSincScaleCount> m;
|
||||
std::array<unsigned int,BSincScaleCount> filterOffset;
|
||||
al::span<const float> Tab;
|
||||
};
|
||||
|
||||
extern const BSincTable gBSinc12;
|
||||
|
|
|
|||
|
|
@ -3,79 +3,3 @@
|
|||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,61 +2,16 @@
|
|||
#define CORE_BUFFER_STORAGE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
#include "storage_formats.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; }
|
||||
|
||||
|
|
@ -85,7 +40,7 @@ struct BufferStorage {
|
|||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
al::span<al::byte> mData;
|
||||
al::span<std::byte> mData;
|
||||
|
||||
uint mSampleRate{0u};
|
||||
FmtChannels mChannels{FmtMono};
|
||||
|
|
@ -97,19 +52,20 @@ struct BufferStorage {
|
|||
AmbiScaling mAmbiScaling{AmbiScaling::FuMa};
|
||||
uint mAmbiOrder{0u};
|
||||
|
||||
inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); }
|
||||
inline uint channelsFromFmt() const noexcept
|
||||
[[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromFmt(mType); }
|
||||
[[nodiscard]] auto channelsFromFmt() const noexcept -> uint
|
||||
{ return ChannelsFromFmt(mChannels, mAmbiOrder); }
|
||||
inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
|
||||
[[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint
|
||||
{ return channelsFromFmt() * bytesFromFmt(); }
|
||||
|
||||
inline uint blockSizeFromFmt() const noexcept
|
||||
[[nodiscard]] auto blockSizeFromFmt() const noexcept -> uint
|
||||
{
|
||||
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); }
|
||||
[[nodiscard]] auto isBFormat() const noexcept -> bool { return IsBFormat(mChannels); }
|
||||
};
|
||||
|
||||
#endif /* CORE_BUFFER_STORAGE_H */
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
* more memory and are harder on cache, while smaller values may need more
|
||||
* iterations for mixing.
|
||||
*/
|
||||
constexpr int BufferLineSize{1024};
|
||||
inline constexpr size_t BufferLineSize{1024};
|
||||
|
||||
using FloatBufferLine = std::array<float,BufferLineSize>;
|
||||
using FloatBufferSpan = al::span<float,BufferLineSize>;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "async_event.h"
|
||||
#include "context.h"
|
||||
|
|
@ -23,52 +27,23 @@ ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
|
|||
|
||||
ContextBase::~ContextBase()
|
||||
{
|
||||
size_t count{0};
|
||||
ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)};
|
||||
if(cprops)
|
||||
{
|
||||
++count;
|
||||
delete cprops;
|
||||
}
|
||||
cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire);
|
||||
while(cprops)
|
||||
{
|
||||
std::unique_ptr<ContextProps> old{cprops};
|
||||
cprops = old->next.load(std::memory_order_relaxed);
|
||||
++count;
|
||||
}
|
||||
TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s");
|
||||
|
||||
count = 0;
|
||||
EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)};
|
||||
while(eprops)
|
||||
{
|
||||
std::unique_ptr<EffectSlotProps> old{eprops};
|
||||
eprops = old->next.load(std::memory_order_relaxed);
|
||||
++count;
|
||||
}
|
||||
TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
|
||||
|
||||
if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)})
|
||||
{
|
||||
al::destroy_n(curarray->end(), curarray->size());
|
||||
delete curarray;
|
||||
}
|
||||
|
||||
delete mVoices.exchange(nullptr, std::memory_order_relaxed);
|
||||
mActiveAuxSlots.store(nullptr, std::memory_order_relaxed);
|
||||
mVoices.store(nullptr, std::memory_order_relaxed);
|
||||
|
||||
if(mAsyncEvents)
|
||||
{
|
||||
count = 0;
|
||||
size_t 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);
|
||||
std::destroy_n(std::launder(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);
|
||||
std::destroy_n(std::launder(reinterpret_cast<AsyncEvent*>(evt_vec.second.buf)),
|
||||
evt_vec.second.len);
|
||||
count += evt_vec.second.len;
|
||||
}
|
||||
if(count > 0)
|
||||
|
|
@ -80,85 +55,131 @@ ContextBase::~ContextBase()
|
|||
|
||||
void ContextBase::allocVoiceChanges()
|
||||
{
|
||||
constexpr size_t clustersize{128};
|
||||
static constexpr size_t clustersize{std::tuple_size_v<VoiceChangeCluster::element_type>};
|
||||
|
||||
VoiceChangeCluster clusterptr{std::make_unique<VoiceChangeCluster::element_type>()};
|
||||
const auto cluster = al::span{*clusterptr};
|
||||
|
||||
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();
|
||||
mVoiceChangeClusters.emplace_back(std::move(clusterptr));
|
||||
mVoiceChangeTail = mVoiceChangeClusters.back()->data();
|
||||
}
|
||||
|
||||
void ContextBase::allocVoiceProps()
|
||||
{
|
||||
constexpr size_t clustersize{32};
|
||||
static constexpr size_t clustersize{std::tuple_size_v<VoicePropsCluster::element_type>};
|
||||
|
||||
TRACE("Increasing allocated voice properties to %zu\n",
|
||||
(mVoicePropClusters.size()+1) * clustersize);
|
||||
|
||||
VoicePropsCluster cluster{std::make_unique<VoicePropsItem[]>(clustersize)};
|
||||
auto clusterptr = std::make_unique<VoicePropsCluster::element_type>();
|
||||
auto cluster = al::span{*clusterptr};
|
||||
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));
|
||||
mVoicePropClusters.emplace_back(std::move(clusterptr));
|
||||
|
||||
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(),
|
||||
mVoicePropClusters.back()->back().next.store(oldhead, std::memory_order_relaxed);
|
||||
} while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back()->data(),
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == false);
|
||||
}
|
||||
|
||||
void ContextBase::allocVoices(size_t addcount)
|
||||
{
|
||||
constexpr size_t clustersize{32};
|
||||
static constexpr size_t clustersize{std::tuple_size_v<VoiceCluster::element_type>};
|
||||
/* Convert element count to cluster count. */
|
||||
addcount = (addcount+(clustersize-1)) / clustersize;
|
||||
|
||||
if(!addcount)
|
||||
{
|
||||
if(!mVoiceClusters.empty())
|
||||
return;
|
||||
++addcount;
|
||||
}
|
||||
|
||||
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));
|
||||
mVoiceClusters.emplace_back(std::make_unique<VoiceCluster::element_type>());
|
||||
--addcount;
|
||||
}
|
||||
|
||||
auto newarray = VoiceArray::Create(totalcount);
|
||||
auto voice_iter = newarray->begin();
|
||||
for(VoiceCluster &cluster : mVoiceClusters)
|
||||
{
|
||||
for(size_t i{0};i < clustersize;++i)
|
||||
*(voice_iter++) = &cluster[i];
|
||||
}
|
||||
voice_iter = std::transform(cluster->begin(), cluster->end(), voice_iter,
|
||||
[](Voice &voice) noexcept -> Voice* { return &voice; });
|
||||
|
||||
if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel))
|
||||
{
|
||||
mDevice->waitForMix();
|
||||
delete oldvoices;
|
||||
}
|
||||
if(auto oldvoices = mVoices.exchange(std::move(newarray), std::memory_order_acq_rel))
|
||||
std::ignore = mDevice->waitForMix();
|
||||
}
|
||||
|
||||
|
||||
void ContextBase::allocEffectSlotProps()
|
||||
{
|
||||
static constexpr size_t clustersize{std::tuple_size_v<EffectSlotPropsCluster::element_type>};
|
||||
|
||||
TRACE("Increasing allocated effect slot properties to %zu\n",
|
||||
(mEffectSlotPropClusters.size()+1) * clustersize);
|
||||
|
||||
auto clusterptr = std::make_unique<EffectSlotPropsCluster::element_type>();
|
||||
auto cluster = al::span{*clusterptr};
|
||||
for(size_t i{1};i < clustersize;++i)
|
||||
cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed);
|
||||
auto *newcluster = mEffectSlotPropClusters.emplace_back(std::move(clusterptr)).get();
|
||||
|
||||
EffectSlotProps *oldhead{mFreeEffectSlotProps.load(std::memory_order_acquire)};
|
||||
do {
|
||||
newcluster->back().next.store(oldhead, std::memory_order_relaxed);
|
||||
} while(mFreeEffectSlotProps.compare_exchange_weak(oldhead, newcluster->data(),
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == false);
|
||||
}
|
||||
|
||||
EffectSlot *ContextBase::getEffectSlot()
|
||||
{
|
||||
for(auto& cluster : mEffectSlotClusters)
|
||||
for(auto& clusterptr : mEffectSlotClusters)
|
||||
{
|
||||
for(size_t i{0};i < EffectSlotClusterSize;++i)
|
||||
{
|
||||
if(!cluster[i].InUse)
|
||||
return &cluster[i];
|
||||
}
|
||||
const auto cluster = al::span{*clusterptr};
|
||||
auto iter = std::find_if_not(cluster.begin(), cluster.end(),
|
||||
std::mem_fn(&EffectSlot::InUse));
|
||||
if(iter != cluster.end()) return al::to_address(iter);
|
||||
}
|
||||
|
||||
if(1 >= std::numeric_limits<int>::max()/EffectSlotClusterSize - mEffectSlotClusters.size())
|
||||
auto clusterptr = std::make_unique<EffectSlotCluster::element_type>();
|
||||
if(1 >= std::numeric_limits<int>::max()/clusterptr->size() - mEffectSlotClusters.size())
|
||||
throw std::runtime_error{"Allocating too many effect slots"};
|
||||
const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize};
|
||||
const size_t totalcount{(mEffectSlotClusters.size()+1) * clusterptr->size()};
|
||||
TRACE("Increasing allocated effect slots to %zu\n", totalcount);
|
||||
|
||||
mEffectSlotClusters.emplace_back(std::make_unique<EffectSlot[]>(EffectSlotClusterSize));
|
||||
return getEffectSlot();
|
||||
mEffectSlotClusters.emplace_back(std::move(clusterptr));
|
||||
return mEffectSlotClusters.back()->data();
|
||||
}
|
||||
|
||||
|
||||
void ContextBase::allocContextProps()
|
||||
{
|
||||
static constexpr size_t clustersize{std::tuple_size_v<ContextPropsCluster::element_type>};
|
||||
|
||||
TRACE("Increasing allocated context properties to %zu\n",
|
||||
(mContextPropClusters.size()+1) * clustersize);
|
||||
|
||||
auto clusterptr = std::make_unique<ContextPropsCluster::element_type>();
|
||||
auto cluster = al::span{*clusterptr};
|
||||
for(size_t i{1};i < clustersize;++i)
|
||||
cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed);
|
||||
auto *newcluster = mContextPropClusters.emplace_back(std::move(clusterptr)).get();
|
||||
|
||||
ContextProps *oldhead{mFreeContextProps.load(std::memory_order_acquire)};
|
||||
do {
|
||||
newcluster->back().next.store(oldhead, std::memory_order_relaxed);
|
||||
} while(mFreeContextProps.compare_exchange_weak(oldhead, newcluster->data(),
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,16 @@
|
|||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alsem.h"
|
||||
#include "alspan.h"
|
||||
#include "async_event.h"
|
||||
#include "atomic.h"
|
||||
#include "bufferline.h"
|
||||
#include "threads.h"
|
||||
#include "flexarray.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vecmat.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct DeviceBase;
|
||||
struct EffectSlot;
|
||||
|
|
@ -25,12 +26,10 @@ struct Voice;
|
|||
struct VoiceChange;
|
||||
struct VoicePropsItem;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
inline constexpr float SpeedOfSoundMetersPerSec{343.3f};
|
||||
|
||||
constexpr float SpeedOfSoundMetersPerSec{343.3f};
|
||||
|
||||
constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */
|
||||
inline constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */
|
||||
|
||||
enum class DistanceModel : unsigned char {
|
||||
Disable,
|
||||
|
|
@ -58,8 +57,6 @@ struct ContextProps {
|
|||
DistanceModel mDistanceModel;
|
||||
|
||||
std::atomic<ContextProps*> next;
|
||||
|
||||
DEF_NEWDEL(ContextProps)
|
||||
};
|
||||
|
||||
struct ContextParams {
|
||||
|
|
@ -87,7 +84,7 @@ struct ContextBase {
|
|||
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
|
||||
* indicates if updates are currently happening).
|
||||
*/
|
||||
RefCount mUpdateCount{0u};
|
||||
std::atomic<unsigned int> mUpdateCount{0u};
|
||||
std::atomic<bool> mHoldUpdates{false};
|
||||
std::atomic<bool> mStopVoicesOnDisconnect{true};
|
||||
|
||||
|
|
@ -98,7 +95,7 @@ struct ContextBase {
|
|||
*/
|
||||
std::atomic<ContextProps*> mFreeContextProps{nullptr};
|
||||
std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
|
||||
std::atomic<EffectSlotProps*> mFreeEffectslotProps{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
|
||||
|
|
@ -110,21 +107,22 @@ struct ContextBase {
|
|||
|
||||
void allocVoiceChanges();
|
||||
void allocVoiceProps();
|
||||
|
||||
void allocEffectSlotProps();
|
||||
void allocContextProps();
|
||||
|
||||
ContextParams mParams;
|
||||
|
||||
using VoiceArray = al::FlexArray<Voice*>;
|
||||
std::atomic<VoiceArray*> mVoices{};
|
||||
al::atomic_unique_ptr<VoiceArray> mVoices{};
|
||||
std::atomic<size_t> mActiveVoiceCount{};
|
||||
|
||||
void allocVoices(size_t addcount);
|
||||
al::span<Voice*> getVoicesSpan() const noexcept
|
||||
[[nodiscard]] auto getVoicesSpan() const noexcept -> al::span<Voice*>
|
||||
{
|
||||
return {mVoices.load(std::memory_order_relaxed)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_relaxed)};
|
||||
}
|
||||
al::span<Voice*> getVoicesSpanAcquired() const noexcept
|
||||
[[nodiscard]] auto getVoicesSpanAcquired() const noexcept -> al::span<Voice*>
|
||||
{
|
||||
return {mVoices.load(std::memory_order_acquire)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_acquire)};
|
||||
|
|
@ -132,12 +130,16 @@ struct ContextBase {
|
|||
|
||||
|
||||
using EffectSlotArray = al::FlexArray<EffectSlot*>;
|
||||
std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};
|
||||
/* This array is split in half. The front half is the list of activated
|
||||
* effect slots as set by the app, and the back half is the same list but
|
||||
* sorted to ensure later effect slots are fed by earlier ones.
|
||||
*/
|
||||
al::atomic_unique_ptr<EffectSlotArray> mActiveAuxSlots;
|
||||
|
||||
std::thread mEventThread;
|
||||
al::semaphore mEventSem;
|
||||
std::unique_ptr<RingBuffer> mAsyncEvents;
|
||||
using AsyncEventBitset = std::bitset<AsyncEvent::UserEventCount>;
|
||||
using AsyncEventBitset = std::bitset<al::to_underlying(AsyncEnableBits::Count)>;
|
||||
std::atomic<AsyncEventBitset> mEnabledEvts{0u};
|
||||
|
||||
/* Asynchronous voice change actions are processed as a linked list of
|
||||
|
|
@ -145,21 +147,29 @@ struct ContextBase {
|
|||
* 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 VoiceChangeCluster = std::unique_ptr<std::array<VoiceChange,128>>;
|
||||
std::vector<VoiceChangeCluster> mVoiceChangeClusters;
|
||||
|
||||
using VoiceCluster = std::unique_ptr<Voice[]>;
|
||||
al::vector<VoiceCluster> mVoiceClusters;
|
||||
using VoiceCluster = std::unique_ptr<std::array<Voice,32>>;
|
||||
std::vector<VoiceCluster> mVoiceClusters;
|
||||
|
||||
using VoicePropsCluster = std::unique_ptr<VoicePropsItem[]>;
|
||||
al::vector<VoicePropsCluster> mVoicePropClusters;
|
||||
using VoicePropsCluster = std::unique_ptr<std::array<VoicePropsItem,32>>;
|
||||
std::vector<VoicePropsCluster> mVoicePropClusters;
|
||||
|
||||
|
||||
static constexpr size_t EffectSlotClusterSize{4};
|
||||
EffectSlot *getEffectSlot();
|
||||
|
||||
using EffectSlotCluster = std::unique_ptr<EffectSlot[]>;
|
||||
al::vector<EffectSlotCluster> mEffectSlotClusters;
|
||||
using EffectSlotCluster = std::unique_ptr<std::array<EffectSlot,4>>;
|
||||
std::vector<EffectSlotCluster> mEffectSlotClusters;
|
||||
|
||||
using EffectSlotPropsCluster = std::unique_ptr<std::array<EffectSlotProps,4>>;
|
||||
std::vector<EffectSlotPropsCluster> mEffectSlotPropClusters;
|
||||
|
||||
/* This could be greater than 2, but there should be no way there can be
|
||||
* more than two context property updates in use simultaneously.
|
||||
*/
|
||||
using ContextPropsCluster = std::unique_ptr<std::array<ContextProps,2>>;
|
||||
std::vector<ContextPropsCluster> mContextPropClusters;
|
||||
|
||||
|
||||
ContextBase(DeviceBase *device);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits.h>
|
||||
#include <climits>
|
||||
|
||||
#include "albit.h"
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "fpu_ctrl.h"
|
||||
|
||||
|
|
@ -24,43 +24,46 @@ static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for Buffer
|
|||
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;
|
||||
constexpr float LoadSample(DevFmtType_t<T> val) noexcept = delete;
|
||||
|
||||
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
|
||||
template<> constexpr float LoadSample<DevFmtByte>(DevFmtType_t<DevFmtByte> val) noexcept
|
||||
{ return float(val) * (1.0f/128.0f); }
|
||||
template<> constexpr float LoadSample<DevFmtShort>(DevFmtType_t<DevFmtShort> val) noexcept
|
||||
{ return float(val) * (1.0f/32768.0f); }
|
||||
template<> constexpr 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
|
||||
template<> constexpr float LoadSample<DevFmtFloat>(DevFmtType_t<DevFmtFloat> val) noexcept
|
||||
{ return val; }
|
||||
|
||||
template<> inline float LoadSample<DevFmtUByte>(DevFmtType_t<DevFmtUByte> val) noexcept
|
||||
template<> constexpr 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
|
||||
template<> constexpr 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
|
||||
template<> constexpr 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
|
||||
inline void LoadSampleArray(const al::span<float> dst, const void *src, const size_t channel,
|
||||
const size_t srcstep) 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]);
|
||||
assert(channel < srcstep);
|
||||
const auto srcspan = al::span{static_cast<const DevFmtType_t<T>*>(src), dst.size()*srcstep};
|
||||
auto ssrc = srcspan.cbegin();
|
||||
std::generate(dst.begin(), dst.end(), [&ssrc,channel,srcstep]
|
||||
{
|
||||
const float ret{LoadSample<T>(ssrc[channel])};
|
||||
ssrc += ptrdiff_t(srcstep);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
void LoadSamples(const al::span<float> dst, const void *src, const size_t channel,
|
||||
const size_t srcstep, const DevFmtType srctype) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
case T: LoadSampleArray<T>(dst, src, channel, srcstep); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
|
|
@ -81,11 +84,11 @@ 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)); }
|
||||
{ return fastf2i(std::clamp(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))); }
|
||||
{ return static_cast<int16_t>(fastf2i(std::clamp(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))); }
|
||||
{ return static_cast<int8_t>(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); }
|
||||
|
||||
/* Define unsigned output variations. */
|
||||
template<> inline uint32_t StoreSample<DevFmtUInt>(float val) noexcept
|
||||
|
|
@ -96,20 +99,25 @@ 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
|
||||
inline void StoreSampleArray(void *dst, const al::span<const float> src, const size_t channel,
|
||||
const size_t dststep) 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]);
|
||||
assert(channel < dststep);
|
||||
const auto dstspan = al::span{static_cast<DevFmtType_t<T>*>(dst), src.size()*dststep};
|
||||
auto sdst = dstspan.begin();
|
||||
std::for_each(src.cbegin(), src.cend(), [&sdst,channel,dststep](const float in)
|
||||
{
|
||||
sdst[channel] = StoreSample<T>(in);
|
||||
sdst += ptrdiff_t(dststep);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype,
|
||||
const size_t samples) noexcept
|
||||
void StoreSamples(void *dst, const al::span<const float> src, const size_t channel,
|
||||
const size_t dststep, const DevFmtType dsttype) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
|
||||
case T: StoreSampleArray<T>(dst, src, channel, dststep); break
|
||||
switch(dsttype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
|
|
@ -125,30 +133,35 @@ void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFm
|
|||
|
||||
|
||||
template<DevFmtType T>
|
||||
void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept
|
||||
void Mono2Stereo(const al::span<float> dst, const void *src) 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;
|
||||
const auto srcspan = al::span{static_cast<const DevFmtType_t<T>*>(src), dst.size()>>1};
|
||||
auto sdst = dst.begin();
|
||||
std::for_each(srcspan.cbegin(), srcspan.cend(), [&sdst](const auto in)
|
||||
{ sdst = std::fill_n(sdst, 2, LoadSample<T>(in)*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
|
||||
void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::span<float> dst,
|
||||
const void *src) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
std::fill_n(dst, frames, 0.0f);
|
||||
const auto srcspan = al::span{static_cast<const DevFmtType_t<T>*>(src), step*dst.size()};
|
||||
std::fill_n(dst.begin(), dst.size(), 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]);
|
||||
auto ssrc = srcspan.cbegin();
|
||||
std::for_each(dst.begin(), dst.end(), [&ssrc,step,c](float &sample)
|
||||
{
|
||||
const float s{LoadSample<T>(ssrc[c])};
|
||||
ssrc += ptrdiff_t(step);
|
||||
sample += s;
|
||||
});
|
||||
}
|
||||
chanmask >>= 1;
|
||||
}
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] *= scale;
|
||||
std::for_each(dst.begin(), dst.end(), [scale](float &sample) noexcept { sample *= scale; });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -168,19 +181,19 @@ SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstTyp
|
|||
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);
|
||||
}
|
||||
chan.PrevSamples.fill(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);
|
||||
const auto step = std::min(std::round(srcRate*double{MixerFracOne}/dstRate),
|
||||
MaxPitch*double{MixerFracOne});
|
||||
converter->mIncrement = std::max(static_cast<uint>(step), 1u);
|
||||
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()); };
|
||||
{
|
||||
converter->mResample = [](const InterpState*, const al::span<const float> src, uint,
|
||||
const uint, const al::span<float> dst)
|
||||
{ std::copy_n(src.begin()+MaxResamplerEdge, dst.size(), dst.begin()); };
|
||||
}
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
|
@ -210,24 +223,25 @@ uint SampleConverter::availableOut(uint srcframes) const
|
|||
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()));
|
||||
return static_cast<uint>(std::clamp((DataSize64 + mIncrement-1)/mIncrement, 1_u64,
|
||||
uint64_t{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 size_t SrcFrameSize{mChan.size() * mSrcTypeSize};
|
||||
const size_t DstFrameSize{mChan.size() * mDstTypeSize};
|
||||
const uint increment{mIncrement};
|
||||
auto SamplesIn = static_cast<const al::byte*>(*src);
|
||||
uint NumSrcSamples{*srcframes};
|
||||
auto SamplesIn = al::span{static_cast<const std::byte*>(*src), NumSrcSamples*SrcFrameSize};
|
||||
auto SamplesOut = al::span{static_cast<std::byte*>(dst), dstframes*DstFrameSize};
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
uint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
const uint readable{minu(NumSrcSamples, BufferLineSize - prepcount)};
|
||||
const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)};
|
||||
|
||||
if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable)
|
||||
{
|
||||
|
|
@ -235,16 +249,16 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
* 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);
|
||||
LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable),
|
||||
SamplesIn.data(), chan, mChan.size(), mSrcType);
|
||||
|
||||
mSrcPrepCount = prepcount + readable;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
float *RESTRICT SrcData{mSrcSamples};
|
||||
float *RESTRICT DstData{mDstSamples};
|
||||
const auto SrcData = al::span<float>{mSrcSamples};
|
||||
const auto DstData = al::span<float>{mDstSamples};
|
||||
uint DataPosFrac{mFracOffset};
|
||||
uint64_t DataSize64{prepcount};
|
||||
DataSize64 += readable;
|
||||
|
|
@ -253,39 +267,36 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
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);
|
||||
auto DstSize = static_cast<uint>(std::clamp((DataSize64 + increment-1)/increment, 1_u64,
|
||||
uint64_t{BufferLineSize}));
|
||||
DstSize = std::min(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)};
|
||||
const uint nextprep{std::min(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);
|
||||
std::copy_n(mChan[chan].PrevSamples.cbegin(), prepcount, SrcData.begin());
|
||||
LoadSamples(SrcData.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(),
|
||||
mSrcType);
|
||||
|
||||
/* 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);
|
||||
auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep,
|
||||
mChan[chan].PrevSamples.begin());
|
||||
std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f);
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
mResample(&mState, SrcData+MaxResamplerEdge, DataPosFrac, increment,
|
||||
{DstData, DstSize});
|
||||
mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize));
|
||||
|
||||
StoreSamples(DstSamples, DstData, mChan.size(), mDstType, DstSize);
|
||||
StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
|
|
@ -295,15 +306,115 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
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;
|
||||
const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)};
|
||||
SamplesIn = SamplesIn.subspan(SrcFrameSize*srcread);
|
||||
NumSrcSamples -= srcread;
|
||||
|
||||
SamplesOut = SamplesOut.subspan(DstFrameSize*DstSize);
|
||||
pos += DstSize;
|
||||
}
|
||||
|
||||
*src = SamplesIn.data();
|
||||
*srcframes = NumSrcSamples;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes)
|
||||
{
|
||||
const auto srcs = al::span{src, mChan.size()};
|
||||
const auto dsts = al::span{dst, mChan.size()};
|
||||
const uint increment{mIncrement};
|
||||
uint NumSrcSamples{*srcframes};
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
uint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
const uint readable{std::min(NumSrcSamples, uint{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++)
|
||||
{
|
||||
auto samples = al::span{static_cast<const std::byte*>(srcs[chan]),
|
||||
NumSrcSamples*size_t{mSrcTypeSize}};
|
||||
LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable),
|
||||
samples.data(), 0, 1, mSrcType);
|
||||
srcs[chan] = samples.subspan(size_t{mSrcTypeSize}*readable).data();
|
||||
}
|
||||
|
||||
mSrcPrepCount = prepcount + readable;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto SrcData = al::span{mSrcSamples};
|
||||
const auto DstData = al::span{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>(std::clamp((DataSize64 + increment-1)/increment, 1_u64,
|
||||
uint64_t{BufferLineSize}));
|
||||
DstSize = std::min(DstSize, dstframes-pos);
|
||||
|
||||
const uint DataPosEnd{DstSize*increment + DataPosFrac};
|
||||
const uint SrcDataEnd{DataPosEnd>>MixerFracBits};
|
||||
|
||||
assert(prepcount+readable >= SrcDataEnd);
|
||||
const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)};
|
||||
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
{
|
||||
/* Load the previous samples into the source data first, then the
|
||||
* new samples from the input buffer.
|
||||
*/
|
||||
auto srciter = std::copy_n(mChan[chan].PrevSamples.cbegin(),prepcount,SrcData.begin());
|
||||
LoadSamples({srciter, readable}, srcs[chan], 0, 1, mSrcType);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep,
|
||||
mChan[chan].PrevSamples.begin());
|
||||
std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f);
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize));
|
||||
|
||||
auto DstSamples = al::span{static_cast<std::byte*>(dsts[chan]),
|
||||
size_t{mDstTypeSize}*dstframes}.subspan(pos*size_t{mDstTypeSize});
|
||||
StoreSamples(DstSamples.data(), DstData.first(DstSize), 0, 1, mDstType);
|
||||
}
|
||||
|
||||
/* 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{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)};
|
||||
std::for_each(srcs.begin(), srcs.end(), [this,NumSrcSamples,srcread](const void *&srcref)
|
||||
{
|
||||
auto srcspan = al::span{static_cast<const std::byte*>(srcref),
|
||||
size_t{mSrcTypeSize}*NumSrcSamples};
|
||||
srcref = srcspan.subspan(size_t{mSrcTypeSize}*srcread).data();
|
||||
});
|
||||
NumSrcSamples -= srcread;
|
||||
|
||||
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
}
|
||||
|
||||
*src = SamplesIn;
|
||||
*srcframes = NumSrcSamples;
|
||||
|
||||
return pos;
|
||||
|
|
@ -317,7 +428,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const
|
|||
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
|
||||
#define HANDLE_FMT(T) case T: Multi2Mono<T>(mChanMask, mSrcStep, scale, {dst, frames}, src); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
|
|
@ -332,7 +443,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const
|
|||
{
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
|
||||
#define HANDLE_FMT(T) case T: Mono2Stereo<T>({dst, frames*2_uz}, src); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
#include "almalloc.h"
|
||||
#include "devformat.h"
|
||||
#include "flexarray.h"
|
||||
#include "mixer/defs.h"
|
||||
#include "resampler_limits.h"
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
|
@ -25,21 +27,22 @@ struct SampleConverter {
|
|||
InterpState mState{};
|
||||
ResamplerFunc mResample{};
|
||||
|
||||
alignas(16) float mSrcSamples[BufferLineSize]{};
|
||||
alignas(16) float mDstSamples[BufferLineSize]{};
|
||||
alignas(16) FloatBufferLine mSrcSamples{};
|
||||
alignas(16) FloatBufferLine mDstSamples{};
|
||||
|
||||
struct ChanSamples {
|
||||
alignas(16) float PrevSamples[MaxResamplerPadding];
|
||||
alignas(16) std::array<float,MaxResamplerPadding> PrevSamples;
|
||||
};
|
||||
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;
|
||||
[[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint;
|
||||
[[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint;
|
||||
[[nodiscard]] auto availableOut(uint srcframes) const -> uint;
|
||||
|
||||
using SampleOffset = std::chrono::duration<int64_t, std::ratio<1,MixerFracOne>>;
|
||||
SampleOffset currentInputDelay() const noexcept
|
||||
[[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset
|
||||
{
|
||||
const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge};
|
||||
return SampleOffset{(prep<<MixerFracBits) + mFracOffset};
|
||||
|
|
@ -58,7 +61,7 @@ struct ChannelConverter {
|
|||
uint mChanMask{};
|
||||
DevFmtChannels mDstChans{};
|
||||
|
||||
bool is_active() const noexcept { return mChanMask != 0; }
|
||||
[[nodiscard]] auto is_active() const noexcept -> bool { return mChanMask != 0; }
|
||||
|
||||
void convert(const void *src, float *dst, uint frames) const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
|
@ -50,14 +51,14 @@ inline std::array<reg_type,4> get_cpuid(unsigned int f)
|
|||
|
||||
} // namespace
|
||||
|
||||
al::optional<CPUInfo> GetCPUInfo()
|
||||
std::optional<CPUInfo> GetCPUInfo()
|
||||
{
|
||||
CPUInfo ret;
|
||||
|
||||
#ifdef CAN_GET_CPUID
|
||||
auto cpuregs = get_cpuid(0);
|
||||
if(cpuregs[0] == 0)
|
||||
return al::nullopt;
|
||||
return std::nullopt;
|
||||
|
||||
const reg_type maxfunc{cpuregs[0]};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#ifndef CORE_CPU_CAPS_H
|
||||
#define CORE_CPU_CAPS_H
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
|
||||
extern int CPUCapFlags;
|
||||
enum {
|
||||
|
|
@ -21,6 +20,6 @@ struct CPUInfo {
|
|||
int mCaps{0};
|
||||
};
|
||||
|
||||
al::optional<CPUInfo> GetCPUInfo();
|
||||
std::optional<CPUInfo> GetCPUInfo();
|
||||
|
||||
#endif /* CORE_CPU_CAPS_H */
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
#ifndef CORE_CUBIC_DEFS_H
|
||||
#define CORE_CUBIC_DEFS_H
|
||||
|
||||
#include <array>
|
||||
|
||||
/* 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];
|
||||
alignas(16) std::array<float,4> mCoeffs;
|
||||
alignas(16) std::array<float,4> mDeltas;
|
||||
};
|
||||
|
||||
#endif /* CORE_CUBIC_DEFS_H */
|
||||
|
|
|
|||
|
|
@ -1,59 +1,121 @@
|
|||
|
||||
#include "cubic_tables.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <cstddef>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "cubic_defs.h"
|
||||
|
||||
/* These gaussian filter tables are inspired by the gaussian-like filter found
|
||||
* in the SNES. This is based on the public domain code developed by Near, with
|
||||
* the help of Ryphecha and nocash, from the nesdev.org forums.
|
||||
*
|
||||
* <https://forums.nesdev.org/viewtopic.php?p=251534#p251534>
|
||||
*
|
||||
* Additional changes were made here, the most obvious being that is has full
|
||||
* floating-point precision instead of 11-bit fixed-point, but also an offset
|
||||
* adjustment for the coefficients to better preserve phase.
|
||||
*/
|
||||
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{};
|
||||
[[nodiscard]]
|
||||
auto GetCoeff(double idx) noexcept -> double
|
||||
{
|
||||
const double k{0.5 + idx};
|
||||
if(k > 512.0) return 0.0;
|
||||
const double s{ std::sin(al::numbers::pi*1.280/1024 * k)};
|
||||
const double t{(std::cos(al::numbers::pi*2.000/1023 * k) - 1.0) * 0.50};
|
||||
const double u{(std::cos(al::numbers::pi*4.000/1023 * k) - 1.0) * 0.08};
|
||||
return s * (t + u + 1.0) / k;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const CubicTable gCubicSpline{SplineFilter.getTable()};
|
||||
GaussianTable::GaussianTable()
|
||||
{
|
||||
static constexpr double IndexScale{512.0 / double{CubicPhaseCount*2}};
|
||||
/* Fill in the main coefficients. */
|
||||
for(std::size_t pi{0};pi < CubicPhaseCount;++pi)
|
||||
{
|
||||
const double coeff0{GetCoeff(static_cast<double>(CubicPhaseCount + pi)*IndexScale)};
|
||||
const double coeff1{GetCoeff(static_cast<double>(pi)*IndexScale)};
|
||||
const double coeff2{GetCoeff(static_cast<double>(CubicPhaseCount - pi)*IndexScale)};
|
||||
const double coeff3{GetCoeff(static_cast<double>(CubicPhaseCount*2_uz-pi)*IndexScale)};
|
||||
|
||||
const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)};
|
||||
mTable[pi].mCoeffs[0] = static_cast<float>(coeff0 * scale);
|
||||
mTable[pi].mCoeffs[1] = static_cast<float>(coeff1 * scale);
|
||||
mTable[pi].mCoeffs[2] = static_cast<float>(coeff2 * scale);
|
||||
mTable[pi].mCoeffs[3] = static_cast<float>(coeff3 * scale);
|
||||
}
|
||||
|
||||
/* Fill in the coefficient deltas. */
|
||||
for(std::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 std::size_t pi{CubicPhaseCount - 1};
|
||||
mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
SplineTable::SplineTable()
|
||||
{
|
||||
/* This filter table is based on a Catmull-Rom spline. It retains more of
|
||||
* the original high-frequency content, at the cost of increased harmonics.
|
||||
*/
|
||||
for(std::size_t pi{0};pi < CubicPhaseCount;++pi)
|
||||
{
|
||||
const double mu{static_cast<double>(pi) / double{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);
|
||||
}
|
||||
|
||||
for(std::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 std::size_t pi{CubicPhaseCount - 1};
|
||||
mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
|
||||
CubicFilter::CubicFilter()
|
||||
{
|
||||
static constexpr double IndexScale{512.0 / double{sTableSteps*2}};
|
||||
/* Only half the coefficients need to be iterated here, since Coeff2 and
|
||||
* Coeff3 are just Coeff1 and Coeff0 in reverse respectively.
|
||||
*/
|
||||
for(size_t i{0};i < sTableSteps/2 + 1;++i)
|
||||
{
|
||||
const double coeff0{GetCoeff(static_cast<double>(sTableSteps + i)*IndexScale)};
|
||||
const double coeff1{GetCoeff(static_cast<double>(i)*IndexScale)};
|
||||
const double coeff2{GetCoeff(static_cast<double>(sTableSteps - i)*IndexScale)};
|
||||
const double coeff3{GetCoeff(static_cast<double>(sTableSteps*2_uz - i)*IndexScale)};
|
||||
|
||||
const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)};
|
||||
mFilter[sTableSteps + i] = static_cast<float>(coeff0 * scale);
|
||||
mFilter[i] = static_cast<float>(coeff1 * scale);
|
||||
mFilter[sTableSteps - i] = static_cast<float>(coeff2 * scale);
|
||||
mFilter[sTableSteps*2 - i] = static_cast<float>(coeff3 * scale);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,41 @@
|
|||
#ifndef CORE_CUBIC_TABLES_H
|
||||
#define CORE_CUBIC_TABLES_H
|
||||
|
||||
#include "alspan.h"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "cubic_defs.h"
|
||||
|
||||
|
||||
struct CubicTable {
|
||||
al::span<const CubicCoefficients,CubicPhaseCount> Tab;
|
||||
std::array<CubicCoefficients,CubicPhaseCount> mTable{};
|
||||
};
|
||||
|
||||
/* 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;
|
||||
struct GaussianTable : CubicTable { GaussianTable(); };
|
||||
inline const GaussianTable gGaussianFilter;
|
||||
|
||||
struct SplineTable : CubicTable { SplineTable(); };
|
||||
inline const SplineTable gSplineFilter;
|
||||
|
||||
|
||||
struct CubicFilter {
|
||||
static constexpr std::size_t sTableBits{8};
|
||||
static constexpr std::size_t sTableSteps{1 << sTableBits};
|
||||
static constexpr std::size_t sTableMask{sTableSteps - 1};
|
||||
|
||||
std::array<float,sTableSteps*2 + 1> mFilter{};
|
||||
|
||||
CubicFilter();
|
||||
|
||||
[[nodiscard]] constexpr
|
||||
auto getCoeff0(std::size_t i) const noexcept -> float { return mFilter[sTableSteps+i]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto getCoeff1(std::size_t i) const noexcept -> float { return mFilter[i]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto getCoeff2(std::size_t i) const noexcept -> float { return mFilter[sTableSteps-i]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto getCoeff3(std::size_t i) const noexcept -> float { return mFilter[sTableSteps*2-i]; }
|
||||
};
|
||||
inline const CubicFilter gCubicTable;
|
||||
|
||||
#endif /* CORE_CUBIC_TABLES_H */
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@
|
|||
#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";
|
||||
const char *libname{"libdbus-1.so.3"};
|
||||
|
||||
dbus_handle = LoadLib(libname);
|
||||
if(!dbus_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", libname);
|
||||
return;
|
||||
}
|
||||
|
||||
auto load_func = [](auto &f, const char *name) -> void
|
||||
{ f = reinterpret_cast<std::remove_reference_t<decltype(f)>>(GetSymbol(dbus_handle, name)); };
|
||||
|
|
@ -33,14 +35,8 @@ void PrepareDBus()
|
|||
} \
|
||||
} while(0);
|
||||
|
||||
dbus_handle = LoadLib(libname);
|
||||
if(!dbus_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", libname);
|
||||
return;
|
||||
}
|
||||
DBUS_FUNCTIONS(LOAD_FUNC)
|
||||
|
||||
DBUS_FUNCTIONS(LOAD_FUNC)
|
||||
#undef LOAD_FUNC
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ 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;
|
||||
inline void *dbus_handle{};
|
||||
#define DECL_FUNC(x) inline decltype(x) *p##x{};
|
||||
DBUS_FUNCTIONS(DECL_FUNC)
|
||||
#undef DECL_FUNC
|
||||
|
||||
|
|
@ -70,9 +70,16 @@ namespace dbus {
|
|||
|
||||
struct Error {
|
||||
Error() { dbus_error_init(&mError); }
|
||||
Error(const Error&) = delete;
|
||||
Error(Error&&) = delete;
|
||||
~Error() { dbus_error_free(&mError); }
|
||||
|
||||
void operator=(const Error&) = delete;
|
||||
void operator=(Error&&) = delete;
|
||||
|
||||
DBusError* operator->() { return &mError; }
|
||||
DBusError &get() { return mError; }
|
||||
|
||||
private:
|
||||
DBusError mError{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept
|
|||
case DevFmtX61: return 7;
|
||||
case DevFmtX71: return 8;
|
||||
case DevFmtX714: return 12;
|
||||
case DevFmtX7144: return 16;
|
||||
case DevFmtX3D71: return 8;
|
||||
case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1);
|
||||
}
|
||||
|
|
@ -60,6 +61,7 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept
|
|||
case DevFmtX61: return "6.1 Surround";
|
||||
case DevFmtX71: return "7.1 Surround";
|
||||
case DevFmtX714: return "7.1.4 Surround";
|
||||
case DevFmtX7144: return "7.1.4.4 Surround";
|
||||
case DevFmtX3D71: return "3D7.1 Surround";
|
||||
case DevFmtAmbi3D: return "Ambisonic 3D";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CORE_DEVFORMAT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
|
@ -25,6 +26,11 @@ enum Channel : unsigned char {
|
|||
TopBackCenter,
|
||||
TopBackRight,
|
||||
|
||||
BottomFrontLeft,
|
||||
BottomFrontRight,
|
||||
BottomBackLeft,
|
||||
BottomBackRight,
|
||||
|
||||
Aux0,
|
||||
Aux1,
|
||||
Aux2,
|
||||
|
|
@ -66,12 +72,13 @@ enum DevFmtChannels : unsigned char {
|
|||
DevFmtX61,
|
||||
DevFmtX71,
|
||||
DevFmtX714,
|
||||
DevFmtX7144,
|
||||
DevFmtX3D71,
|
||||
DevFmtAmbi3D,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
};
|
||||
#define MAX_OUTPUT_CHANNELS 16
|
||||
inline constexpr std::size_t MaxOutputChannels{16};
|
||||
|
||||
/* DevFmtType traits, providing the type, etc given a DevFmtType. */
|
||||
template<DevFmtType T>
|
||||
|
|
|
|||
|
|
@ -9,15 +9,12 @@
|
|||
#include "mastering.h"
|
||||
|
||||
|
||||
al::FlexArray<ContextBase*> DeviceBase::sEmptyContextArray{0u};
|
||||
static_assert(std::atomic<std::chrono::nanoseconds>::is_always_lock_free);
|
||||
|
||||
|
||||
DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray}
|
||||
DeviceBase::DeviceBase(DeviceType type)
|
||||
: Type{type}, mContexts{al::FlexArray<ContextBase*>::Create(0)}
|
||||
{
|
||||
}
|
||||
|
||||
DeviceBase::~DeviceBase()
|
||||
{
|
||||
auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed);
|
||||
if(oldarray != &sEmptyContextArray) delete oldarray;
|
||||
}
|
||||
DeviceBase::~DeviceBase() = default;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
#ifndef CORE_DEVICE_H
|
||||
#define CORE_DEVICE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
|
@ -18,6 +17,7 @@
|
|||
#include "bufferline.h"
|
||||
#include "devformat.h"
|
||||
#include "filters/nfc.h"
|
||||
#include "flexarray.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "opthelpers.h"
|
||||
|
|
@ -26,8 +26,10 @@
|
|||
#include "vector.h"
|
||||
|
||||
class BFormatDec;
|
||||
namespace Bs2b {
|
||||
struct bs2b;
|
||||
struct Compressor;
|
||||
} // namespace Bs2b
|
||||
class Compressor;
|
||||
struct ContextBase;
|
||||
struct DirectHrtfState;
|
||||
struct HrtfStore;
|
||||
|
|
@ -35,28 +37,28 @@ struct HrtfStore;
|
|||
using uint = unsigned int;
|
||||
|
||||
|
||||
#define MIN_OUTPUT_RATE 8000
|
||||
#define MAX_OUTPUT_RATE 192000
|
||||
#define DEFAULT_OUTPUT_RATE 48000
|
||||
inline constexpr std::size_t MinOutputRate{8000};
|
||||
inline constexpr std::size_t MaxOutputRate{192000};
|
||||
inline constexpr std::size_t DefaultOutputRate{48000};
|
||||
|
||||
#define DEFAULT_UPDATE_SIZE 960 /* 20ms */
|
||||
#define DEFAULT_NUM_UPDATES 3
|
||||
inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */
|
||||
inline constexpr std::size_t DefaultNumUpdates{3};
|
||||
|
||||
|
||||
enum class DeviceType : unsigned char {
|
||||
enum class DeviceType : std::uint8_t {
|
||||
Playback,
|
||||
Capture,
|
||||
Loopback
|
||||
};
|
||||
|
||||
|
||||
enum class RenderMode : unsigned char {
|
||||
enum class RenderMode : std::uint8_t {
|
||||
Normal,
|
||||
Pairwise,
|
||||
Hrtf
|
||||
};
|
||||
|
||||
enum class StereoEncoding : unsigned char {
|
||||
enum class StereoEncoding : std::uint8_t {
|
||||
Basic,
|
||||
Uhj,
|
||||
Hrtf,
|
||||
|
|
@ -78,24 +80,23 @@ struct DistanceComp {
|
|||
static constexpr uint MaxDelay{1024};
|
||||
|
||||
struct ChanData {
|
||||
al::span<float> Buffer{}; /* Valid size is [0...MaxDelay). */
|
||||
float Gain{1.0f};
|
||||
uint Length{0u}; /* Valid range is [0...MaxDelay). */
|
||||
float *Buffer{nullptr};
|
||||
};
|
||||
|
||||
std::array<ChanData,MAX_OUTPUT_CHANNELS> mChannels;
|
||||
std::array<ChanData,MaxOutputChannels> mChannels;
|
||||
al::FlexArray<float,16> mSamples;
|
||||
|
||||
DistanceComp(size_t count) : mSamples{count} { }
|
||||
DistanceComp(std::size_t count) : mSamples{count} { }
|
||||
|
||||
static std::unique_ptr<DistanceComp> Create(size_t numsamples)
|
||||
static std::unique_ptr<DistanceComp> Create(std::size_t numsamples)
|
||||
{ return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(DistanceComp, mSamples)
|
||||
};
|
||||
|
||||
|
||||
constexpr uint InvalidChannelIndex{~0u};
|
||||
constexpr auto InvalidChannelIndex = static_cast<std::uint8_t>(~0u);
|
||||
|
||||
struct BFChannelConfig {
|
||||
float Scale;
|
||||
|
|
@ -113,24 +114,24 @@ struct MixParams {
|
|||
* 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.
|
||||
* destination channel is InvalidChannelIndex, 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)
|
||||
const std::size_t numIn{inmix.Buffer.size()};
|
||||
const std::size_t numOut{Buffer.size()};
|
||||
for(std::size_t i{0};i < numIn;++i)
|
||||
{
|
||||
auto idx = InvalidChannelIndex;
|
||||
auto gain = 0.0f;
|
||||
std::uint8_t idx{InvalidChannelIndex};
|
||||
float gain{0.0f};
|
||||
|
||||
for(size_t j{0};j < numOut;++j)
|
||||
for(std::size_t j{0};j < numOut;++j)
|
||||
{
|
||||
if(AmbiMap[j].Index == inmix.AmbiMap[i].Index)
|
||||
{
|
||||
idx = static_cast<uint>(j);
|
||||
idx = static_cast<std::uint8_t>(j);
|
||||
gain = AmbiMap[j].Scale * gainbase;
|
||||
break;
|
||||
}
|
||||
|
|
@ -142,7 +143,7 @@ struct MixParams {
|
|||
|
||||
struct RealMixParams {
|
||||
al::span<const InputRemixMap> RemixMap;
|
||||
std::array<uint,MaxChannels> ChannelIndex{};
|
||||
std::array<std::uint8_t,MaxChannels> ChannelIndex{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
|
@ -159,22 +160,26 @@ enum {
|
|||
|
||||
// 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,
|
||||
|
||||
/* Specifies if output is using speaker virtualization (e.g. Windows
|
||||
* Spatial Audio).
|
||||
*/
|
||||
Virtualization,
|
||||
|
||||
DeviceFlagsCount
|
||||
};
|
||||
|
||||
struct DeviceBase {
|
||||
/* To avoid extraneous allocations, a 0-sized FlexArray<ContextBase*> is
|
||||
* defined globally as a sharable object.
|
||||
*/
|
||||
static al::FlexArray<ContextBase*> sEmptyContextArray;
|
||||
enum class DeviceState : std::uint8_t {
|
||||
Unprepared,
|
||||
Configured,
|
||||
Playing
|
||||
};
|
||||
|
||||
struct DeviceBase {
|
||||
std::atomic<bool> Connected{true};
|
||||
const DeviceType Type{};
|
||||
|
||||
|
|
@ -198,6 +203,7 @@ struct DeviceBase {
|
|||
|
||||
// Device flags
|
||||
std::bitset<DeviceFlagsCount> Flags{};
|
||||
DeviceState mDeviceState{DeviceState::Unprepared};
|
||||
|
||||
uint NumAuxSends{};
|
||||
|
||||
|
|
@ -214,35 +220,31 @@ struct DeviceBase {
|
|||
*/
|
||||
NfcFilter mNFCtrlFilter{};
|
||||
|
||||
uint SamplesDone{0u};
|
||||
std::chrono::nanoseconds ClockBase{0};
|
||||
std::atomic<uint> mSamplesDone{0u};
|
||||
std::atomic<std::chrono::nanoseconds> mClockBase{std::chrono::nanoseconds{}};
|
||||
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;
|
||||
static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
|
||||
static constexpr std::size_t MixerChannelsMax{16};
|
||||
alignas(16) std::array<float,MixerLineSize*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];
|
||||
};
|
||||
alignas(16) std::array<float,BufferLineSize> FilteredData{};
|
||||
alignas(16) std::array<float,BufferLineSize+HrtfHistoryLength> ExtraSampleData{};
|
||||
|
||||
/* Persistent storage for HRTF mixing. */
|
||||
alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength];
|
||||
alignas(16) std::array<float2,BufferLineSize+HrirLength> HrtfAccumData{};
|
||||
|
||||
/* 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]{};
|
||||
std::array<uint,MaxAmbiOrder+1> NumChannelsPerOrder{};
|
||||
|
||||
/* "Real" output, which will be written to the device buffer. May alias the
|
||||
* dry buffer.
|
||||
|
|
@ -261,7 +263,7 @@ struct DeviceBase {
|
|||
std::unique_ptr<BFormatDec> AmbiDecoder;
|
||||
|
||||
/* Stereo-to-binaural filter */
|
||||
std::unique_ptr<bs2b> Bs2b;
|
||||
std::unique_ptr<Bs2b::bs2b> Bs2b;
|
||||
|
||||
using PostProc = void(DeviceBase::*)(const size_t SamplesToDo);
|
||||
PostProc PostProcess{nullptr};
|
||||
|
|
@ -280,10 +282,10 @@ struct DeviceBase {
|
|||
* 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};
|
||||
std::atomic<uint> mMixCount{0u};
|
||||
|
||||
// Contexts created on this device
|
||||
std::atomic<al::FlexArray<ContextBase*>*> mContexts{nullptr};
|
||||
al::atomic_unique_ptr<al::FlexArray<ContextBase*>> mContexts;
|
||||
|
||||
|
||||
DeviceBase(DeviceType type);
|
||||
|
|
@ -291,33 +293,69 @@ struct DeviceBase {
|
|||
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(); }
|
||||
[[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); }
|
||||
[[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
|
||||
[[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); }
|
||||
|
||||
uint waitForMix() const noexcept
|
||||
struct MixLock {
|
||||
DeviceBase *const self;
|
||||
const uint mEndVal;
|
||||
|
||||
MixLock(DeviceBase *device, const uint endval) noexcept : self{device}, mEndVal{endval} { }
|
||||
MixLock(const MixLock&) = delete;
|
||||
void operator=(const MixLock&) = delete;
|
||||
/* Update the mix count when the lock goes out of scope to "release" it
|
||||
* (lsb should be 0).
|
||||
*/
|
||||
~MixLock() { self->mMixCount.store(mEndVal, std::memory_order_release); }
|
||||
};
|
||||
auto getWriteMixLock() noexcept -> MixLock
|
||||
{
|
||||
uint refcount;
|
||||
while((refcount=MixCount.load(std::memory_order_acquire))&1) {
|
||||
}
|
||||
/* Increment the mix count at the start of mixing and writing clock
|
||||
* info (lsb should be 1).
|
||||
*/
|
||||
auto mixCount = mMixCount.load(std::memory_order_relaxed);
|
||||
mMixCount.store(++mixCount, std::memory_order_release);
|
||||
return MixLock{this, ++mixCount};
|
||||
}
|
||||
|
||||
/** Waits for the mixer to not be mixing or updating the clock. */
|
||||
[[nodiscard]] auto waitForMix() const noexcept -> uint
|
||||
{
|
||||
uint refcount{mMixCount.load(std::memory_order_acquire)};
|
||||
while((refcount&1)) refcount = mMixCount.load(std::memory_order_acquire);
|
||||
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);
|
||||
/**
|
||||
* Helper to get the current clock time from the device's ClockBase, and
|
||||
* SamplesDone converted from the sample rate. Should only be called while
|
||||
* watching the MixCount.
|
||||
*/
|
||||
[[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds
|
||||
{
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
inline void postProcess(const size_t SamplesToDo)
|
||||
auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency;
|
||||
return mClockBase.load(std::memory_order_relaxed) + ns;
|
||||
}
|
||||
|
||||
void ProcessHrtf(const std::size_t SamplesToDo);
|
||||
void ProcessAmbiDec(const std::size_t SamplesToDo);
|
||||
void ProcessAmbiDecStablized(const std::size_t SamplesToDo);
|
||||
void ProcessUhj(const std::size_t SamplesToDo);
|
||||
void ProcessBs2b(const std::size_t SamplesToDo);
|
||||
|
||||
inline void postProcess(const std::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);
|
||||
void renderSamples(void *outBuffer, const uint numSamples, const std::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)]]
|
||||
#ifdef __MINGW32__
|
||||
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
|
||||
#else
|
||||
[[gnu::format(printf,2,3)]]
|
||||
#endif
|
||||
|
|
@ -325,21 +363,21 @@ struct DeviceBase {
|
|||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
* InvalidChannelIndex if it doesn't exist.
|
||||
*/
|
||||
uint channelIdxByName(Channel chan) const noexcept
|
||||
[[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t
|
||||
{ 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"
|
||||
[[nodiscard]] constexpr
|
||||
auto GetMixerThreadName() noexcept -> const char* { return "alsoft-mixer"; }
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
[[nodiscard]] constexpr
|
||||
auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; }
|
||||
|
||||
#endif /* CORE_DEVICE_H */
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
#ifndef CORE_EFFECTS_BASE_H
|
||||
#define CORE_EFFECTS_BASE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
|
@ -19,21 +18,21 @@ struct RealMixParams;
|
|||
|
||||
|
||||
/** Target gain for the reverb decay feedback reaching the decay time. */
|
||||
constexpr float ReverbDecayGain{0.001f}; /* -60 dB */
|
||||
inline constexpr float ReverbDecayGain{0.001f}; /* -60 dB */
|
||||
|
||||
constexpr float ReverbMaxReflectionsDelay{0.3f};
|
||||
constexpr float ReverbMaxLateReverbDelay{0.1f};
|
||||
inline constexpr float ReverbMaxReflectionsDelay{0.3f};
|
||||
inline constexpr float ReverbMaxLateReverbDelay{0.1f};
|
||||
|
||||
enum class ChorusWaveform {
|
||||
Sinusoid,
|
||||
Triangle
|
||||
};
|
||||
|
||||
constexpr float ChorusMaxDelay{0.016f};
|
||||
constexpr float FlangerMaxDelay{0.004f};
|
||||
inline constexpr float ChorusMaxDelay{0.016f};
|
||||
inline constexpr float FlangerMaxDelay{0.004f};
|
||||
|
||||
constexpr float EchoMaxDelay{0.207f};
|
||||
constexpr float EchoMaxLRDelay{0.404f};
|
||||
inline constexpr float EchoMaxDelay{0.207f};
|
||||
inline constexpr float EchoMaxLRDelay{0.404f};
|
||||
|
||||
enum class FShifterDirection {
|
||||
Down,
|
||||
|
|
@ -59,115 +58,135 @@ enum class VMorpherWaveform {
|
|||
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 ReverbProps {
|
||||
float Density;
|
||||
float Diffusion;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float GainLF;
|
||||
float DecayTime;
|
||||
float DecayHFRatio;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsGain;
|
||||
float ReflectionsDelay;
|
||||
std::array<float,3> ReflectionsPan;
|
||||
float LateReverbGain;
|
||||
float LateReverbDelay;
|
||||
std::array<float,3> LateReverbPan;
|
||||
float EchoTime;
|
||||
float EchoDepth;
|
||||
float ModulationTime;
|
||||
float ModulationDepth;
|
||||
float AirAbsorptionGainHF;
|
||||
float HFReference;
|
||||
float LFReference;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
};
|
||||
|
||||
struct AutowahProps {
|
||||
float AttackTime;
|
||||
float ReleaseTime;
|
||||
float Resonance;
|
||||
float PeakGain;
|
||||
};
|
||||
|
||||
struct ChorusProps {
|
||||
ChorusWaveform Waveform;
|
||||
int Phase;
|
||||
float Rate;
|
||||
float Depth;
|
||||
float Feedback;
|
||||
float Delay;
|
||||
};
|
||||
|
||||
struct CompressorProps {
|
||||
bool OnOff;
|
||||
};
|
||||
|
||||
struct DistortionProps {
|
||||
float Edge;
|
||||
float Gain;
|
||||
float LowpassCutoff;
|
||||
float EQCenter;
|
||||
float EQBandwidth;
|
||||
};
|
||||
|
||||
struct EchoProps {
|
||||
float Delay;
|
||||
float LRDelay;
|
||||
|
||||
float Damping;
|
||||
float Feedback;
|
||||
|
||||
float Spread;
|
||||
};
|
||||
|
||||
struct EqualizerProps {
|
||||
float LowCutoff;
|
||||
float LowGain;
|
||||
float Mid1Center;
|
||||
float Mid1Gain;
|
||||
float Mid1Width;
|
||||
float Mid2Center;
|
||||
float Mid2Gain;
|
||||
float Mid2Width;
|
||||
float HighCutoff;
|
||||
float HighGain;
|
||||
};
|
||||
|
||||
struct FshifterProps {
|
||||
float Frequency;
|
||||
FShifterDirection LeftDirection;
|
||||
FShifterDirection RightDirection;
|
||||
};
|
||||
|
||||
struct ModulatorProps {
|
||||
float Frequency;
|
||||
float HighPassCutoff;
|
||||
ModulatorWaveform Waveform;
|
||||
};
|
||||
|
||||
struct PshifterProps {
|
||||
int CoarseTune;
|
||||
int FineTune;
|
||||
};
|
||||
|
||||
struct VmorpherProps {
|
||||
float Rate;
|
||||
VMorpherPhenome PhonemeA;
|
||||
VMorpherPhenome PhonemeB;
|
||||
int PhonemeACoarseTuning;
|
||||
int PhonemeBCoarseTuning;
|
||||
VMorpherWaveform Waveform;
|
||||
};
|
||||
|
||||
struct DedicatedProps {
|
||||
enum TargetType : bool { Dialog, Lfe };
|
||||
TargetType Target;
|
||||
float Gain;
|
||||
};
|
||||
|
||||
struct ConvolutionProps {
|
||||
std::array<float,3> OrientAt;
|
||||
std::array<float,3> OrientUp;
|
||||
};
|
||||
|
||||
using EffectProps = std::variant<std::monostate,
|
||||
ReverbProps,
|
||||
AutowahProps,
|
||||
ChorusProps,
|
||||
CompressorProps,
|
||||
DistortionProps,
|
||||
EchoProps,
|
||||
EqualizerProps,
|
||||
FshifterProps,
|
||||
ModulatorProps,
|
||||
PshifterProps,
|
||||
VmorpherProps,
|
||||
DedicatedProps,
|
||||
ConvolutionProps>;
|
||||
|
||||
|
||||
struct EffectTarget {
|
||||
MixParams *Main;
|
||||
|
|
@ -189,8 +208,14 @@ struct EffectState : public al::intrusive_ref<EffectState> {
|
|||
|
||||
|
||||
struct EffectStateFactory {
|
||||
EffectStateFactory() = default;
|
||||
EffectStateFactory(const EffectStateFactory&) = delete;
|
||||
EffectStateFactory(EffectStateFactory&&) = delete;
|
||||
virtual ~EffectStateFactory() = default;
|
||||
|
||||
void operator=(const EffectStateFactory&) = delete;
|
||||
void operator=(EffectStateFactory&&) = delete;
|
||||
|
||||
virtual al::intrusive_ptr<EffectState> create() = 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,13 @@
|
|||
|
||||
#include "effectslot.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <cstddef>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "context.h"
|
||||
|
||||
|
||||
EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept
|
||||
std::unique_ptr<EffectSlotArray> EffectSlot::CreatePtrArray(size_t count)
|
||||
{
|
||||
/* 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);
|
||||
return std::unique_ptr<EffectSlotArray>{new(FamCount{count}) EffectSlotArray(count)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
#define CORE_EFFECTSLOT_H
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "device.h"
|
||||
#include "effects/base.h"
|
||||
#include "flexarray.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct EffectSlot;
|
||||
|
|
@ -18,20 +19,18 @@ enum class EffectSlotType : unsigned char {
|
|||
None,
|
||||
Reverb,
|
||||
Chorus,
|
||||
Distortion,
|
||||
Echo,
|
||||
Flanger,
|
||||
FrequencyShifter,
|
||||
VocalMorpher,
|
||||
PitchShifter,
|
||||
RingModulator,
|
||||
Autowah,
|
||||
Compressor,
|
||||
Convolution,
|
||||
Dedicated,
|
||||
Distortion,
|
||||
Echo,
|
||||
Equalizer,
|
||||
EAXReverb,
|
||||
DedicatedLFE,
|
||||
DedicatedDialog,
|
||||
Convolution
|
||||
Flanger,
|
||||
FrequencyShifter,
|
||||
PitchShifter,
|
||||
RingModulator,
|
||||
VocalMorpher,
|
||||
};
|
||||
|
||||
struct EffectSlotProps {
|
||||
|
|
@ -45,8 +44,6 @@ struct EffectSlotProps {
|
|||
al::intrusive_ptr<EffectState> State;
|
||||
|
||||
std::atomic<EffectSlotProps*> next;
|
||||
|
||||
DEF_NEWDEL(EffectSlotProps)
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -81,9 +78,7 @@ struct EffectSlot {
|
|||
al::vector<FloatBufferLine,16> mWetBuffer;
|
||||
|
||||
|
||||
static EffectSlotArray *CreatePtrArray(size_t count) noexcept;
|
||||
|
||||
DEF_NEWDEL(EffectSlot)
|
||||
static std::unique_ptr<EffectSlotArray> CreatePtrArray(size_t count);
|
||||
};
|
||||
|
||||
#endif /* CORE_EFFECTSLOT_H */
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ namespace al {
|
|||
|
||||
base_exception::~base_exception() = default;
|
||||
|
||||
void base_exception::setMessage(const char* msg, std::va_list args)
|
||||
void base_exception::setMessage(const char *msg, std::va_list args)
|
||||
{
|
||||
/* NOLINTBEGIN(*-array-to-pointer-decay) */
|
||||
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);
|
||||
std::vsnprintf(mMessage.data(), mMessage.length(), msg, args2);
|
||||
mMessage.pop_back();
|
||||
}
|
||||
va_end(args2);
|
||||
/* NOLINTEND(*-array-to-pointer-decay) */
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
|
|
|||
|
|
@ -13,19 +13,20 @@ 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);
|
||||
auto setMessage(const char *msg, std::va_list args) -> void;
|
||||
|
||||
public:
|
||||
const char *what() const noexcept override { return mMessage.c_str(); }
|
||||
base_exception() = default;
|
||||
base_exception(const base_exception&) = default;
|
||||
base_exception(base_exception&&) = default;
|
||||
~base_exception() override;
|
||||
|
||||
auto operator=(const base_exception&) -> base_exception& = default;
|
||||
auto operator=(base_exception&&) -> base_exception& = default;
|
||||
|
||||
[[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); }
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#define START_API_FUNC try
|
||||
|
||||
#define END_API_FUNC catch(...) { std::terminate(); }
|
||||
|
||||
#endif /* CORE_EXCEPT_H */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "biquad.h"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
|
@ -27,8 +28,8 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
|||
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 };
|
||||
std::array<Real,3> a{{1.0f, 0.0f, 0.0f}};
|
||||
std::array<Real,3> b{{1.0f, 0.0f, 0.0f}};
|
||||
|
||||
/* Calculate filter coefficients depending on filter type */
|
||||
switch(type)
|
||||
|
|
@ -94,7 +95,7 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
|||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
||||
void BiquadFilterR<Real>::process(const al::span<const Real> src, const al::span<Real> dst)
|
||||
{
|
||||
const Real b0{mB0};
|
||||
const Real b1{mB1};
|
||||
|
|
@ -119,7 +120,7 @@ void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
|||
z2 = input*b2 - output*a2;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample);
|
||||
|
||||
mZ1 = z1;
|
||||
mZ2 = z2;
|
||||
|
|
@ -127,7 +128,7 @@ void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
|||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::dualProcess(BiquadFilterR &other, const al::span<const Real> src,
|
||||
Real *dst)
|
||||
const al::span<Real> dst)
|
||||
{
|
||||
const Real b00{mB0};
|
||||
const Real b01{mB1};
|
||||
|
|
@ -156,7 +157,7 @@ void BiquadFilterR<Real>::dualProcess(BiquadFilterR &other, const al::span<const
|
|||
z12 = input*b12 - output*a12;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample);
|
||||
|
||||
mZ1 = z01;
|
||||
mZ2 = z02;
|
||||
|
|
|
|||
|
|
@ -114,14 +114,15 @@ public:
|
|||
mA2 = other.mA2;
|
||||
}
|
||||
|
||||
void process(const al::span<const Real> src, Real *dst);
|
||||
void process(const al::span<const Real> src, const al::span<Real> dst);
|
||||
/** Processes this filter and the other at the same time. */
|
||||
void dualProcess(BiquadFilterR &other, const al::span<const Real> src, Real *dst);
|
||||
void dualProcess(BiquadFilterR &other, const al::span<const Real> src,
|
||||
const al::span<Real> dst);
|
||||
|
||||
/* Rather hacky. It's just here to support "manual" processing. */
|
||||
std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
|
||||
[[nodiscard]] auto getComponents() const noexcept -> std::pair<Real,Real> { 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
|
||||
[[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real
|
||||
{
|
||||
const Real out{in*mB0 + z1};
|
||||
z1 = in*mB1 - out*mA1 + z2;
|
||||
|
|
@ -134,7 +135,7 @@ template<typename Real>
|
|||
struct DualBiquadR {
|
||||
BiquadFilterR<Real> &f0, &f1;
|
||||
|
||||
void process(const al::span<const Real> src, Real *dst)
|
||||
void process(const al::span<const Real> src, const al::span<Real> dst)
|
||||
{ f0.dualProcess(f1, src, dst); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -48,24 +48,22 @@
|
|||
|
||||
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 }
|
||||
constexpr std::array B{
|
||||
std::array{ 0.0f, 0.0f, 0.0f, 0.0f},
|
||||
std::array{ 1.0f, 0.0f, 0.0f, 0.0f},
|
||||
std::array{ 3.0f, 3.0f, 0.0f, 0.0f},
|
||||
std::array{3.6778f, 6.4595f, 2.3222f, 0.0f},
|
||||
std::array{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;
|
||||
float r{0.5f * w1};
|
||||
float b_00{B[1][0] * r};
|
||||
float g_0{1.0f + b_00};
|
||||
|
||||
nfc.base_gain = 1.0f / g_0;
|
||||
nfc.a1 = 2.0f * b_00 / g_0;
|
||||
|
|
@ -95,14 +93,12 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
|
|||
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;
|
||||
float r{0.5f * w1};
|
||||
float b_10{B[2][0] * r};
|
||||
float b_11{B[2][1] * r * r};
|
||||
float 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;
|
||||
|
|
@ -137,17 +133,14 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
|
|||
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;
|
||||
float r{0.5f * w1};
|
||||
float b_10{B[3][0] * r};
|
||||
float b_11{B[3][1] * r * r};
|
||||
float b_00{B[3][2] * r};
|
||||
float g_1{1.0f + b_10 + b_11};
|
||||
float 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;
|
||||
|
|
@ -189,18 +182,15 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
|
|||
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;
|
||||
float r{0.5f * w1};
|
||||
float b_10{B[4][0] * r};
|
||||
float b_11{B[4][1] * r * r};
|
||||
float b_00{B[4][2] * r};
|
||||
float b_01{B[4][3] * r * r};
|
||||
float g_1{1.0f + b_10 + b_11};
|
||||
float 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;
|
||||
|
|
@ -262,7 +252,7 @@ void NfcFilter::adjust(const float w0) noexcept
|
|||
}
|
||||
|
||||
|
||||
void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
|
||||
void NfcFilter::process1(const al::span<const float> src, const al::span<float> dst)
|
||||
{
|
||||
const float gain{first.gain};
|
||||
const float b1{first.b1};
|
||||
|
|
@ -275,11 +265,11 @@ void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
|
|||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample);
|
||||
first.z[0] = z1;
|
||||
}
|
||||
|
||||
void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
|
||||
void NfcFilter::process2(const al::span<const float> src, const al::span<float> dst)
|
||||
{
|
||||
const float gain{second.gain};
|
||||
const float b1{second.b1};
|
||||
|
|
@ -296,12 +286,12 @@ void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
|
|||
z1 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample);
|
||||
second.z[0] = z1;
|
||||
second.z[1] = z2;
|
||||
}
|
||||
|
||||
void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
|
||||
void NfcFilter::process3(const al::span<const float> src, const al::span<float> dst)
|
||||
{
|
||||
const float gain{third.gain};
|
||||
const float b1{third.b1};
|
||||
|
|
@ -325,13 +315,13 @@ void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
|
|||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), 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)
|
||||
void NfcFilter::process4(const al::span<const float> src, const al::span<float> dst)
|
||||
{
|
||||
const float gain{fourth.gain};
|
||||
const float b1{fourth.b1};
|
||||
|
|
@ -359,7 +349,7 @@ void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
|
|||
z3 += y;
|
||||
return out;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample);
|
||||
fourth.z[0] = z1;
|
||||
fourth.z[1] = z2;
|
||||
fourth.z[2] = z3;
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
#ifndef CORE_FILTERS_NFC_H
|
||||
#define CORE_FILTERS_NFC_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
struct NfcFilter1 {
|
||||
float base_gain, gain;
|
||||
float b1, a1;
|
||||
float z[1];
|
||||
float base_gain{1.0f}, gain{1.0f};
|
||||
float b1{}, a1{};
|
||||
std::array<float,1> z{};
|
||||
};
|
||||
struct NfcFilter2 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, a1, a2;
|
||||
float z[2];
|
||||
float base_gain{1.0f}, gain{1.0f};
|
||||
float b1{}, b2{}, a1{}, a2{};
|
||||
std::array<float,2> z{};
|
||||
};
|
||||
struct NfcFilter3 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, a1, a2, a3;
|
||||
float z[3];
|
||||
float base_gain{1.0f}, gain{1.0f};
|
||||
float b1{}, b2{}, b3{}, a1{}, a2{}, a3{};
|
||||
std::array<float,3> z{};
|
||||
};
|
||||
struct NfcFilter4 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, b4, a1, a2, a3, a4;
|
||||
float z[4];
|
||||
float base_gain{1.0f}, gain{1.0f};
|
||||
float b1{}, b2{}, b3{}, b4{}, a1{}, a2{}, a3{}, a4{};
|
||||
std::array<float,4> z{};
|
||||
};
|
||||
|
||||
class NfcFilter {
|
||||
|
|
@ -39,7 +40,7 @@ public:
|
|||
* 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
|
||||
* average speaker distance, or based on the reference delay if outputting
|
||||
* NFC-HOA. It must not be negative, 0, or infinite. The source distance
|
||||
* should not be too small relative to the control distance.
|
||||
*/
|
||||
|
|
@ -48,16 +49,16 @@ public:
|
|||
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);
|
||||
void process1(const al::span<const float> src, const al::span<float> dst);
|
||||
|
||||
/* Near-field control filter for second-order ambisonic channels (4-8). */
|
||||
void process2(const al::span<const float> src, float *RESTRICT dst);
|
||||
void process2(const al::span<const float> src, const al::span<float> dst);
|
||||
|
||||
/* Near-field control filter for third-order ambisonic channels (9-15). */
|
||||
void process3(const al::span<const float> src, float *RESTRICT dst);
|
||||
void process3(const al::span<const float> src, const al::span<float> dst);
|
||||
|
||||
/* Near-field control filter for fourth-order ambisonic channels (16-24). */
|
||||
void process4(const al::span<const float> src, float *RESTRICT dst);
|
||||
void process4(const al::span<const float> src, const al::span<float> dst);
|
||||
};
|
||||
|
||||
#endif /* CORE_FILTERS_NFC_H */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "splitter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
|
|
@ -27,14 +28,17 @@ void BandSplitterR<Real>::init(Real f0norm)
|
|||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout, Real *lpout)
|
||||
void BandSplitterR<Real>::process(const al::span<const Real> input, const al::span<Real> hpout,
|
||||
const al::span<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
|
||||
assert(lpout.size() <= input.size());
|
||||
auto lpiter = lpout.begin();
|
||||
auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpiter](const Real in) noexcept -> Real
|
||||
{
|
||||
/* Low-pass sample processing. */
|
||||
Real d{(in - lp_z1) * lp_coeff};
|
||||
|
|
@ -45,7 +49,7 @@ void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout,
|
|||
lp_y = lp_z2 + d;
|
||||
lp_z2 = lp_y + d;
|
||||
|
||||
*(lpout++) = lp_y;
|
||||
*(lpiter++) = lp_y;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
Real ap_y{in*ap_coeff + ap_z1};
|
||||
|
|
@ -54,15 +58,15 @@ void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout,
|
|||
/* High-pass generated from removing low-passed output. */
|
||||
return ap_y - lp_y;
|
||||
};
|
||||
std::transform(input.cbegin(), input.cend(), hpout, proc_sample);
|
||||
std::transform(input.cbegin(), input.cend(), hpout.begin(), 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)
|
||||
void BandSplitterR<Real>::processHfScale(const al::span<const Real> input,
|
||||
const al::span<Real> output, const Real hfscale)
|
||||
{
|
||||
const Real ap_coeff{mCoeff};
|
||||
const Real lp_coeff{mCoeff*0.5f + 0.5f};
|
||||
|
|
@ -89,7 +93,7 @@ void BandSplitterR<Real>::processHfScale(const al::span<const Real> input, Real
|
|||
*/
|
||||
return (ap_y-lp_y)*hfscale + lp_y;
|
||||
};
|
||||
std::transform(input.begin(), input.end(), output, proc_sample);
|
||||
std::transform(input.cbegin(), input.cend(), output.begin(), proc_sample);
|
||||
mLpZ1 = lp_z1;
|
||||
mLpZ2 = lp_z2;
|
||||
mApZ1 = ap_z1;
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ public:
|
|||
|
||||
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 process(const al::span<const Real> input, const al::span<Real> hpout,
|
||||
const al::span<Real> lpout);
|
||||
|
||||
void processHfScale(const al::span<const Real> input, Real *output, const Real hfscale);
|
||||
void processHfScale(const al::span<const Real> input, const al::span<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);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
namespace al {
|
||||
|
||||
const int16_t muLawDecompressionTable[256] = {
|
||||
const std::array<int16_t,256> muLawDecompressionTable{{
|
||||
-32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
|
||||
-23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
|
||||
-15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
|
||||
|
|
@ -39,9 +39,9 @@ const int16_t muLawDecompressionTable[256] = {
|
|||
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] = {
|
||||
const std::array<int16_t,256> aLawDecompressionTable{{
|
||||
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
|
||||
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
|
||||
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
|
||||
|
|
@ -74,6 +74,6 @@ const int16_t aLawDecompressionTable[256] = {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
#ifndef CORE_FMT_TRAITS_H
|
||||
#define CORE_FMT_TRAITS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "buffer_storage.h"
|
||||
#include "storage_formats.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
extern const int16_t muLawDecompressionTable[256];
|
||||
extern const int16_t aLawDecompressionTable[256];
|
||||
extern const std::array<std::int16_t,256> muLawDecompressionTable;
|
||||
extern const std::array<std::int16_t,256> aLawDecompressionTable;
|
||||
|
||||
|
||||
template<FmtType T>
|
||||
|
|
@ -19,63 +18,52 @@ struct FmtTypeTraits { };
|
|||
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtUByte> {
|
||||
using Type = uint8_t;
|
||||
using Type = std::uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return val*OutT{1.0/128.0} - OutT{1.0}; }
|
||||
constexpr float operator()(const Type val) const noexcept
|
||||
{ return float(val)*(1.0f/128.0f) - 1.0f; }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtShort> {
|
||||
using Type = int16_t;
|
||||
using Type = std::int16_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept { return val*OutT{1.0/32768.0}; }
|
||||
constexpr float operator()(const Type val) const noexcept
|
||||
{ return float(val) * (1.0f/32768.0f); }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtInt> {
|
||||
using Type = std::int32_t;
|
||||
|
||||
constexpr float operator()(const Type val) const noexcept
|
||||
{ return static_cast<float>(val)*(1.0f/2147483648.0f); }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtFloat> {
|
||||
using Type = float;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept { return val; }
|
||||
constexpr float operator()(const Type val) const 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); }
|
||||
constexpr float operator()(const Type val) const noexcept { return static_cast<float>(val); }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtMulaw> {
|
||||
using Type = uint8_t;
|
||||
using Type = std::uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return muLawDecompressionTable[val] * OutT{1.0/32768.0}; }
|
||||
constexpr float operator()(const Type val) const noexcept
|
||||
{ return float(muLawDecompressionTable[val]) * (1.0f/32768.0f); }
|
||||
};
|
||||
template<>
|
||||
struct FmtTypeTraits<FmtAlaw> {
|
||||
using Type = uint8_t;
|
||||
using Type = std::uint8_t;
|
||||
|
||||
template<typename OutT>
|
||||
static constexpr inline OutT to(const Type val) noexcept
|
||||
{ return aLawDecompressionTable[val] * OutT{1.0/32768.0}; }
|
||||
constexpr float operator()(const Type val) const noexcept
|
||||
{ return float(aLawDecompressionTable[val]) * (1.0f/32768.0f); }
|
||||
};
|
||||
|
||||
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -8,54 +8,80 @@
|
|||
#endif
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <emmintrin.h>
|
||||
#ifndef _MM_DENORMALS_ZERO_MASK
|
||||
#elif defined(HAVE_SSE)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_SSE) && !defined(_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"
|
||||
|
||||
namespace {
|
||||
|
||||
void FPUCtl::enter() noexcept
|
||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
[[gnu::target("sse")]]
|
||||
#endif
|
||||
[[maybe_unused]]
|
||||
void disable_denormals(unsigned int *state [[maybe_unused]])
|
||||
{
|
||||
if(this->in_mode) return;
|
||||
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
this->sse_state = _mm_getcsr();
|
||||
unsigned int sseState{this->sse_state};
|
||||
*state = _mm_getcsr();
|
||||
unsigned int sseState{*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)
|
||||
#elif defined(HAVE_SSE)
|
||||
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
*state = _mm_getcsr();
|
||||
unsigned int sseState{*state};
|
||||
sseState &= ~_MM_FLUSH_ZERO_MASK;
|
||||
sseState |= _MM_FLUSH_ZERO_ON;
|
||||
if((CPUCapFlags&CPU_CAP_SSE2))
|
||||
{
|
||||
__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));
|
||||
sseState &= ~_MM_DENORMALS_ZERO_MASK;
|
||||
sseState |= _MM_DENORMALS_ZERO_ON;
|
||||
}
|
||||
_mm_setcsr(sseState);
|
||||
#endif
|
||||
|
||||
this->in_mode = true;
|
||||
}
|
||||
|
||||
void FPUCtl::leave() noexcept
|
||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
[[gnu::target("sse")]]
|
||||
#endif
|
||||
[[maybe_unused]]
|
||||
void reset_fpu(unsigned int state [[maybe_unused]])
|
||||
{
|
||||
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));
|
||||
#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE)
|
||||
_mm_setcsr(state);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
unsigned int FPUCtl::Set() noexcept
|
||||
{
|
||||
unsigned int state{};
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
disable_denormals(&state);
|
||||
#elif defined(HAVE_SSE)
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
disable_denormals(&state);
|
||||
#endif
|
||||
return state;
|
||||
}
|
||||
|
||||
void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept
|
||||
{
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
reset_fpu(state);
|
||||
#elif defined(HAVE_SSE)
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
reset_fpu(state);
|
||||
#endif
|
||||
this->in_mode = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,31 @@
|
|||
#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{};
|
||||
|
||||
static unsigned int Set() noexcept;
|
||||
static void Reset(unsigned int state) noexcept;
|
||||
|
||||
public:
|
||||
FPUCtl() noexcept { enter(); in_mode = true; }
|
||||
~FPUCtl() { if(in_mode) leave(); }
|
||||
FPUCtl() noexcept : sse_state{Set()}, in_mode{true} { }
|
||||
~FPUCtl() { if(in_mode) Reset(sse_state); }
|
||||
|
||||
FPUCtl(const FPUCtl&) = delete;
|
||||
FPUCtl& operator=(const FPUCtl&) = delete;
|
||||
|
||||
void enter() noexcept;
|
||||
void leave() noexcept;
|
||||
void enter() noexcept
|
||||
{
|
||||
if(!in_mode)
|
||||
sse_state = Set();
|
||||
in_mode = true;
|
||||
}
|
||||
void leave() noexcept
|
||||
{
|
||||
if(in_mode)
|
||||
Reset(sse_state);
|
||||
in_mode = false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* CORE_FPU_CTRL_H */
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "almalloc.h"
|
||||
#include "bufferline.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "flexarray.h"
|
||||
|
||||
|
||||
struct FrontStablizer {
|
||||
|
|
|
|||
|
|
@ -3,102 +3,64 @@
|
|||
|
||||
#include "helpers.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <system_error>
|
||||
|
||||
#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());
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
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;
|
||||
std::mutex gSearchLock;
|
||||
|
||||
void DirectorySearch(const std::filesystem::path &path, const std::string_view ext,
|
||||
std::vector<std::string> *const results)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
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);
|
||||
try {
|
||||
auto fpath = path.lexically_normal();
|
||||
if(!fs::exists(fpath))
|
||||
return;
|
||||
|
||||
const al::span<std::string> newlist{results->data()+base, results->size()-base};
|
||||
TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data());
|
||||
for(auto&& dirent : fs::directory_iterator{fpath})
|
||||
{
|
||||
auto&& entrypath = dirent.path();
|
||||
if(!entrypath.has_extension())
|
||||
continue;
|
||||
|
||||
if(fs::status(entrypath).type() == fs::file_type::regular
|
||||
&& al::case_compare(entrypath.extension().u8string(), ext) == 0)
|
||||
results->emplace_back(entrypath.u8string());
|
||||
}
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Exception enumerating files: %s\n", e.what());
|
||||
}
|
||||
|
||||
const auto newlist = al::span{*results}.subspan(base);
|
||||
std::sort(newlist.begin(), newlist.end());
|
||||
for(const auto &name : newlist)
|
||||
TRACE(" got %s\n", name.c_str());
|
||||
|
|
@ -106,83 +68,127 @@ void DirectorySearch(const char *path, const char *ext, al::vector<std::string>
|
|||
|
||||
} // namespace
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
||||
{
|
||||
auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); };
|
||||
#ifdef _WIN32
|
||||
|
||||
static std::mutex search_lock;
|
||||
std::lock_guard<std::mutex> _{search_lock};
|
||||
#include <cctype>
|
||||
#include <shlobj.h>
|
||||
|
||||
const PathNamePair &GetProcBinary()
|
||||
{
|
||||
auto get_procbin = []
|
||||
{
|
||||
#if !defined(ALSOFT_UWP)
|
||||
DWORD pathlen{256};
|
||||
auto fullpath = std::wstring(pathlen, L'\0');
|
||||
DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)};
|
||||
while(len == fullpath.size())
|
||||
{
|
||||
pathlen <<= 1;
|
||||
if(pathlen == 0)
|
||||
{
|
||||
/* pathlen overflow (more than 4 billion characters??) */
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
fullpath.resize(pathlen);
|
||||
len = GetModuleFileNameW(nullptr, fullpath.data(), pathlen);
|
||||
}
|
||||
if(len == 0)
|
||||
{
|
||||
ERR("Failed to get process name: error %lu\n", GetLastError());
|
||||
return PathNamePair{};
|
||||
}
|
||||
|
||||
fullpath.resize(len);
|
||||
#else
|
||||
const WCHAR *exePath{__wargv[0]};
|
||||
if(!exePath)
|
||||
{
|
||||
ERR("Failed to get process name: __wargv[0] == nullptr\n");
|
||||
return PathNamePair{};
|
||||
}
|
||||
std::wstring fullpath{exePath};
|
||||
#endif
|
||||
std::replace(fullpath.begin(), fullpath.end(), L'/', L'\\');
|
||||
|
||||
PathNamePair res{};
|
||||
if(auto seppos = fullpath.rfind(L'\\'); seppos < fullpath.size())
|
||||
{
|
||||
res.path = wstr_to_utf8(std::wstring_view{fullpath}.substr(0, seppos));
|
||||
res.fname = wstr_to_utf8(std::wstring_view{fullpath}.substr(seppos+1));
|
||||
}
|
||||
else
|
||||
res.fname = wstr_to_utf8(fullpath);
|
||||
|
||||
TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str());
|
||||
return res;
|
||||
};
|
||||
static const PathNamePair procbin{get_procbin()};
|
||||
return procbin;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
|
||||
struct CoTaskMemDeleter {
|
||||
void operator()(void *mem) const { CoTaskMemFree(mem); }
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
|
||||
{
|
||||
std::lock_guard<std::mutex> srchlock{gSearchLock};
|
||||
|
||||
/* If the path is absolute, use it directly. */
|
||||
al::vector<std::string> results;
|
||||
if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
|
||||
std::vector<std::string> results;
|
||||
auto path = std::filesystem::u8path(subdir);
|
||||
if(path.is_absolute())
|
||||
{
|
||||
std::string path{subdir};
|
||||
std::replace(path.begin(), path.end(), '/', '\\');
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
DirectorySearch(path, 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);
|
||||
DirectorySearch(*localpath, ext, &results);
|
||||
else if(auto curpath = std::filesystem::current_path(); !curpath.empty())
|
||||
DirectorySearch(curpath, ext, &results);
|
||||
|
||||
#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
|
||||
/* Search the local and global data dirs. */
|
||||
static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
|
||||
for(int id : ids)
|
||||
for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData})
|
||||
{
|
||||
WCHAR buffer[MAX_PATH];
|
||||
if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
|
||||
std::unique_ptr<WCHAR,CoTaskMemDeleter> buffer;
|
||||
const HRESULT hr{SHGetKnownFolderPath(folderid, KF_FLAG_DONT_UNEXPAND, nullptr,
|
||||
al::out_ptr(buffer))};
|
||||
if(FAILED(hr) || !buffer || !*buffer)
|
||||
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);
|
||||
DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results);
|
||||
}
|
||||
#endif
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void SetRTPriority(void)
|
||||
void SetRTPriority()
|
||||
{
|
||||
#if !defined(ALSOFT_UWP)
|
||||
if(RTPrioLevel > 0)
|
||||
{
|
||||
if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
|
||||
ERR("Failed to set priority level for thread\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
|
@ -197,7 +203,6 @@ void SetRTPriority(void)
|
|||
#include <sched.h>
|
||||
#endif
|
||||
#ifdef HAVE_RTKIT
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include "dbus_wrap.h"
|
||||
|
|
@ -209,184 +214,112 @@ void SetRTPriority(void)
|
|||
|
||||
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
|
||||
auto get_procbin = []
|
||||
{
|
||||
pathname.resize(pathlen + 1);
|
||||
sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0);
|
||||
pathname.resize(pathlen);
|
||||
}
|
||||
std::string pathname;
|
||||
#ifdef __FreeBSD__
|
||||
size_t pathlen{};
|
||||
std::array<int,4> mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}};
|
||||
if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1)
|
||||
WARN("Failed to sysctl kern.proc.pathname: %s\n",
|
||||
std::generic_category().message(errno).c_str());
|
||||
else
|
||||
{
|
||||
auto procpath = std::vector<char>(pathlen+1, '\0');
|
||||
sysctl(mib.data(), mib.size(), procpath.data(), &pathlen, nullptr, 0);
|
||||
pathname = procpath.data();
|
||||
}
|
||||
#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));
|
||||
}
|
||||
if(pathname.empty())
|
||||
{
|
||||
std::array<char,PROC_PIDPATHINFO_MAXSIZE> procpath{};
|
||||
const pid_t pid{getpid()};
|
||||
if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1)
|
||||
ERR("proc_pidpath(%d, ...) failed: %s\n", pid,
|
||||
std::generic_category().message(errno).c_str());
|
||||
else
|
||||
pathname = procpath.data();
|
||||
}
|
||||
#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));
|
||||
}
|
||||
if(pathname.empty())
|
||||
{
|
||||
std::array<char,PATH_MAX> procpath{};
|
||||
if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath.data(), procpath.size()) == B_OK)
|
||||
pathname = procpath.data();
|
||||
}
|
||||
#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)
|
||||
if(pathname.empty())
|
||||
{
|
||||
selfname = name;
|
||||
len = readlink(selfname, pathname.data(), pathname.size());
|
||||
if(len >= 0 || errno != ENOENT) break;
|
||||
}
|
||||
const std::array SelfLinkNames{
|
||||
"/proc/self/exe"sv,
|
||||
"/proc/self/file"sv,
|
||||
"/proc/curproc/exe"sv,
|
||||
"/proc/curproc/file"sv,
|
||||
};
|
||||
|
||||
while(len > 0 && static_cast<size_t>(len) == pathname.size())
|
||||
{
|
||||
pathname.resize(pathname.size() << 1);
|
||||
len = readlink(selfname, pathname.data(), pathname.size());
|
||||
for(const std::string_view name : SelfLinkNames)
|
||||
{
|
||||
try {
|
||||
if(!std::filesystem::exists(name))
|
||||
continue;
|
||||
if(auto path = std::filesystem::read_symlink(name); !path.empty())
|
||||
{
|
||||
pathname = path.u8string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
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()));
|
||||
PathNamePair res{};
|
||||
if(auto seppos = pathname.rfind('/'); seppos < pathname.size())
|
||||
{
|
||||
res.path = std::string_view{pathname}.substr(0, seppos);
|
||||
res.fname = std::string_view{pathname}.substr(seppos+1);
|
||||
}
|
||||
else
|
||||
res.fname = pathname;
|
||||
|
||||
TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str());
|
||||
return *procbin;
|
||||
TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str());
|
||||
return res;
|
||||
};
|
||||
static const PathNamePair procbin{get_procbin()};
|
||||
return procbin;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
|
||||
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
|
||||
{
|
||||
TRACE("Searching %s for *%s\n", path, ext);
|
||||
DIR *dir{opendir(path)};
|
||||
if(!dir) return;
|
||||
std::lock_guard<std::mutex> srchlock{gSearchLock};
|
||||
|
||||
const auto base = results->size();
|
||||
const size_t extlen{strlen(ext)};
|
||||
|
||||
while(struct dirent *dirent{readdir(dir)})
|
||||
std::vector<std::string> results;
|
||||
auto path = std::filesystem::u8path(subdir);
|
||||
if(path.is_absolute())
|
||||
{
|
||||
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);
|
||||
DirectorySearch(path, 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();
|
||||
}
|
||||
}
|
||||
DirectorySearch(*localpath, ext, &results);
|
||||
else if(auto curpath = std::filesystem::current_path(); !curpath.empty())
|
||||
DirectorySearch(curpath, ext, &results);
|
||||
|
||||
// Search local data dir
|
||||
/* 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);
|
||||
}
|
||||
DirectorySearch(std::filesystem::path{*datapath}/path, 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);
|
||||
}
|
||||
DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results);
|
||||
|
||||
// Search global data dirs
|
||||
/* Search global data dirs */
|
||||
std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
|
||||
|
||||
size_t curpos{0u};
|
||||
|
|
@ -394,30 +327,19 @@ al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
|||
{
|
||||
size_t nextpos{datadirs.find(':', curpos)};
|
||||
|
||||
std::string path{(nextpos != std::string::npos) ?
|
||||
datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)};
|
||||
std::string_view pathname{(nextpos != std::string::npos)
|
||||
? std::string_view{datadirs}.substr(curpos, nextpos++ - curpos)
|
||||
: std::string_view{datadirs}.substr(curpos)};
|
||||
curpos = nextpos;
|
||||
|
||||
if(path.empty()) continue;
|
||||
if(path.back() != '/')
|
||||
path += '/';
|
||||
path += subdir;
|
||||
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
if(!pathname.empty())
|
||||
DirectorySearch(std::filesystem::path{pathname}/path, 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);
|
||||
}
|
||||
}
|
||||
/* Search the installation data directory */
|
||||
if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty())
|
||||
DirectorySearch(instpath/path, ext, &results);
|
||||
#endif
|
||||
|
||||
return results;
|
||||
|
|
@ -425,7 +347,7 @@ al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
|||
|
||||
namespace {
|
||||
|
||||
bool SetRTPriorityPthread(int prio)
|
||||
bool SetRTPriorityPthread(int prio [[maybe_unused]])
|
||||
{
|
||||
int err{ENOTSUP};
|
||||
#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
|
||||
|
|
@ -438,23 +360,20 @@ bool SetRTPriorityPthread(int prio)
|
|||
rtmax = (rtmax-rtmin)/2 + rtmin;
|
||||
|
||||
struct sched_param param{};
|
||||
param.sched_priority = clampi(prio, rtmin, rtmax);
|
||||
param.sched_priority = std::clamp(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);
|
||||
WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(),
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetRTPriorityRTKit(int prio)
|
||||
bool SetRTPriorityRTKit(int prio [[maybe_unused]])
|
||||
{
|
||||
#ifdef HAVE_RTKIT
|
||||
if(!HasDBus())
|
||||
|
|
@ -478,7 +397,7 @@ bool SetRTPriorityRTKit(int prio)
|
|||
if(err == -ENOENT)
|
||||
{
|
||||
err = std::abs(err);
|
||||
ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err);
|
||||
ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err);
|
||||
return false;
|
||||
}
|
||||
int rtmax{rtkit_get_max_realtime_priority(conn.get())};
|
||||
|
|
@ -514,19 +433,20 @@ bool SetRTPriorityRTKit(int prio)
|
|||
err = limit_rttime(conn.get());
|
||||
if(err != 0)
|
||||
WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
|
||||
std::strerror(err), err);
|
||||
std::generic_category().message(err).c_str(), err);
|
||||
}
|
||||
|
||||
/* Limit the maximum real-time priority to half. */
|
||||
rtmax = (rtmax+1)/2;
|
||||
prio = clampi(prio, 1, rtmax);
|
||||
prio = std::clamp(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);
|
||||
WARN("Failed to set real-time priority: %s (%d)\n",
|
||||
std::generic_category().message(err).c_str(), 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
|
||||
|
|
@ -541,13 +461,13 @@ bool SetRTPriorityRTKit(int prio)
|
|||
if(err == 0) return true;
|
||||
|
||||
err = std::abs(err);
|
||||
WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err);
|
||||
WARN("Failed to set high priority: %s (%d)\n",
|
||||
std::generic_category().message(err).c_str(), err);
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
|
||||
#else
|
||||
|
||||
std::ignore = prio;
|
||||
WARN("D-Bus not supported\n");
|
||||
#endif
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -2,17 +2,23 @@
|
|||
#define CORE_HELPERS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "vector.h"
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
||||
struct PathNamePair { std::string path, fname; };
|
||||
const PathNamePair &GetProcBinary(void);
|
||||
struct PathNamePair {
|
||||
std::string path, fname;
|
||||
};
|
||||
const PathNamePair &GetProcBinary();
|
||||
|
||||
extern int RTPrioLevel;
|
||||
extern bool AllowRTTimeLimit;
|
||||
void SetRTPriority(void);
|
||||
/* Mixing thread priority level */
|
||||
inline int RTPrioLevel{1};
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
|
||||
/* Allow reducing the process's RTTime limit for RTKit. */
|
||||
inline bool AllowRTTimeLimit{true};
|
||||
|
||||
void SetRTPriority();
|
||||
|
||||
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir);
|
||||
|
||||
#endif /* CORE_HELPERS_H */
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,21 +4,23 @@
|
|||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "ambidefs.h"
|
||||
#include "bufferline.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
#include "flexarray.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "vector.h"
|
||||
#include "mixer/hrtfdefs.h"
|
||||
|
||||
|
||||
struct HrtfStore {
|
||||
RefCount mRef;
|
||||
struct alignas(16) HrtfStore {
|
||||
std::atomic<uint> mRef;
|
||||
|
||||
uint mSampleRate : 24;
|
||||
uint mIrSize : 8;
|
||||
|
|
@ -36,17 +38,24 @@ struct HrtfStore {
|
|||
ushort azCount;
|
||||
ushort irOffset;
|
||||
};
|
||||
Elevation *mElev;
|
||||
const HrirArray *mCoeffs;
|
||||
const ubyte2 *mDelays;
|
||||
al::span<Elevation> mElev;
|
||||
al::span<const HrirArray> mCoeffs;
|
||||
al::span<const ubyte2> mDelays;
|
||||
|
||||
void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs,
|
||||
const al::span<uint,2> delays);
|
||||
void getCoeffs(float elevation, float azimuth, float distance, float spread,
|
||||
const HrirSpan coeffs, const al::span<uint,2> delays) const;
|
||||
|
||||
void add_ref();
|
||||
void dec_ref();
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
void *operator new(size_t) = delete;
|
||||
void *operator new[](size_t) = delete;
|
||||
void operator delete[](void*) noexcept = delete;
|
||||
|
||||
void operator delete(gsl::owner<void*> block, void*) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); }
|
||||
void operator delete(gsl::owner<void*> block) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); }
|
||||
};
|
||||
using HrtfStorePtr = al::intrusive_ptr<HrtfStore>;
|
||||
|
||||
|
|
@ -60,7 +69,7 @@ struct AngularPoint {
|
|||
|
||||
|
||||
struct DirectHrtfState {
|
||||
std::array<float,BufferLineSize> mTemp;
|
||||
std::array<float,BufferLineSize> mTemp{};
|
||||
|
||||
/* HRTF filter state for dry buffer content */
|
||||
uint mIrSize{0};
|
||||
|
|
@ -74,7 +83,8 @@ struct DirectHrtfState {
|
|||
* 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 al::span<const AngularPoint> AmbiPoints,
|
||||
const al::span<const std::array<float,MaxAmbiChannels>> AmbiMatrix,
|
||||
const float XOverFreq, const al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain);
|
||||
|
||||
static std::unique_ptr<DirectHrtfState> Create(size_t num_chans);
|
||||
|
|
@ -83,7 +93,7 @@ struct DirectHrtfState {
|
|||
};
|
||||
|
||||
|
||||
al::vector<std::string> EnumerateHrtf(al::optional<std::string> pathopt);
|
||||
HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate);
|
||||
std::vector<std::string> EnumerateHrtf(std::optional<std::string> pathopt);
|
||||
HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate);
|
||||
|
||||
#endif /* CORE_HRTF_H */
|
||||
|
|
|
|||
|
|
@ -3,13 +3,19 @@
|
|||
|
||||
#include "logging.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -19,47 +25,108 @@
|
|||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
||||
|
||||
FILE *gLogFile{stderr};
|
||||
#ifdef _DEBUG
|
||||
LogLevel gLogLevel{LogLevel::Warning};
|
||||
#else
|
||||
LogLevel gLogLevel{LogLevel::Error};
|
||||
#endif
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
enum class LogState : uint8_t {
|
||||
FirstRun,
|
||||
Ready,
|
||||
Disable
|
||||
};
|
||||
|
||||
std::mutex LogCallbackMutex;
|
||||
LogState gLogState{LogState::FirstRun};
|
||||
|
||||
LogCallbackFunc gLogCallback{};
|
||||
void *gLogCallbackPtr{};
|
||||
|
||||
constexpr auto GetLevelCode(LogLevel level) noexcept -> std::optional<char>
|
||||
{
|
||||
switch(level)
|
||||
{
|
||||
case LogLevel::Disable: break;
|
||||
case LogLevel::Error: return 'E';
|
||||
case LogLevel::Warning: return 'W';
|
||||
case LogLevel::Trace: return 'I';
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void al_set_log_callback(LogCallbackFunc callback, void *userptr)
|
||||
{
|
||||
auto cblock = std::lock_guard{LogCallbackMutex};
|
||||
gLogCallback = callback;
|
||||
gLogCallbackPtr = callback ? userptr : nullptr;
|
||||
if(gLogState == LogState::FirstRun)
|
||||
{
|
||||
auto extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK");
|
||||
if(!extlogopt || *extlogopt != "1")
|
||||
gLogState = LogState::Ready;
|
||||
else
|
||||
gLogState = LogState::Disable;
|
||||
}
|
||||
}
|
||||
|
||||
void al_print(LogLevel level, const char *fmt, ...) noexcept
|
||||
try {
|
||||
/* 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>();
|
||||
auto prefix = al::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;
|
||||
case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break;
|
||||
case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break;
|
||||
case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break;
|
||||
}
|
||||
|
||||
al::vector<char> dynmsg;
|
||||
std::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()};
|
||||
|
||||
/* NOLINTBEGIN(*-array-to-pointer-decay) */
|
||||
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
|
||||
if(msglen >= 0)
|
||||
{
|
||||
dynmsg.resize(static_cast<size_t>(msglen)+prefix.size() + 1u);
|
||||
if(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()};
|
||||
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);
|
||||
std::vsnprintf(msg.data(), msg.size(), fmt, args2);
|
||||
}
|
||||
msg = msg.first(static_cast<size_t>(msglen));
|
||||
}
|
||||
else
|
||||
msg = {msg.data(), std::strlen(msg.data())};
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
/* NOLINTEND(*-array-to-pointer-decay) */
|
||||
|
||||
if(gLogLevel >= level)
|
||||
{
|
||||
auto logfile = gLogFile;
|
||||
fputs(str, logfile);
|
||||
fflush(logfile);
|
||||
}
|
||||
|
|
@ -86,4 +153,24 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
|||
};
|
||||
__android_log_print(android_severity(level), "openal", "%s", str);
|
||||
#endif
|
||||
|
||||
auto cblock = std::lock_guard{LogCallbackMutex};
|
||||
if(gLogState != LogState::Disable)
|
||||
{
|
||||
while(!msg.empty() && std::isspace(msg.back()))
|
||||
{
|
||||
msg.back() = '\0';
|
||||
msg = msg.first(msg.size()-1);
|
||||
}
|
||||
if(auto logcode = GetLevelCode(level); logcode && !msg.empty())
|
||||
{
|
||||
if(gLogCallback)
|
||||
gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast<int>(msg.size()));
|
||||
else if(gLogState == LogState::FirstRun)
|
||||
gLogState = LogState::Disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
/* Swallow any exceptions */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
#ifndef CORE_LOGGING_H
|
||||
#define CORE_LOGGING_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "opthelpers.h"
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
enum class LogLevel {
|
||||
|
|
@ -16,36 +14,23 @@ extern LogLevel gLogLevel;
|
|||
|
||||
extern FILE *gLogFile;
|
||||
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,3,4)]]
|
||||
|
||||
using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept;
|
||||
|
||||
void al_set_log_callback(LogCallbackFunc callback, void *userptr);
|
||||
|
||||
|
||||
#ifdef __MINGW32__
|
||||
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
|
||||
#else
|
||||
[[gnu::format(printf,3,4)]]
|
||||
[[gnu::format(printf,2,3)]]
|
||||
#endif
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...);
|
||||
void al_print(LogLevel level, const char *fmt, ...) noexcept;
|
||||
|
||||
#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 TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__)
|
||||
|
||||
#define WARN(...) do { \
|
||||
if(gLogLevel >= LogLevel::Warning) UNLIKELY \
|
||||
al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
#define WARN(...) al_print(LogLevel::Warning, __VA_ARGS__)
|
||||
|
||||
#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
|
||||
#define ERR(...) al_print(LogLevel::Error, __VA_ARGS__)
|
||||
|
||||
#endif /* CORE_LOGGING_H */
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
|
@ -21,8 +20,8 @@
|
|||
static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2");
|
||||
|
||||
struct SlidingHold {
|
||||
alignas(16) float mValues[BufferLineSize];
|
||||
uint mExpiries[BufferLineSize];
|
||||
alignas(16) FloatBufferLine mValues;
|
||||
std::array<uint,BufferLineSize> mExpiries;
|
||||
uint mLowerIndex;
|
||||
uint mUpperIndex;
|
||||
uint mLength;
|
||||
|
|
@ -31,7 +30,9 @@ struct SlidingHold {
|
|||
|
||||
namespace {
|
||||
|
||||
using namespace std::placeholders;
|
||||
template<std::size_t A, typename T, std::size_t N>
|
||||
constexpr auto assume_aligned_span(const al::span<T,N> s) noexcept -> al::span<T,N>
|
||||
{ return al::span<T,N>{al::assume_aligned<A>(s.data()), s.size()}; }
|
||||
|
||||
/* 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.
|
||||
|
|
@ -44,8 +45,8 @@ 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;
|
||||
const al::span values{Hold->mValues};
|
||||
const al::span expiries{Hold->mExpiries};
|
||||
uint lowerIndex{Hold->mLowerIndex};
|
||||
uint upperIndex{Hold->mUpperIndex};
|
||||
|
||||
|
|
@ -60,14 +61,16 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
|
|||
}
|
||||
else
|
||||
{
|
||||
do {
|
||||
auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool
|
||||
{
|
||||
do {
|
||||
if(!(in >= values[lowerIndex]))
|
||||
goto found_place;
|
||||
return true;
|
||||
} while(lowerIndex--);
|
||||
return false;
|
||||
};
|
||||
while(!findLowerIndex())
|
||||
lowerIndex = mask;
|
||||
} while(true);
|
||||
found_place:
|
||||
|
||||
lowerIndex = (lowerIndex + 1) & mask;
|
||||
values[lowerIndex] = in;
|
||||
|
|
@ -82,38 +85,41 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
|
|||
|
||||
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)
|
||||
auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex;
|
||||
if(Hold->mLowerIndex < Hold->mUpperIndex)
|
||||
{
|
||||
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_upper, Hold->mExpiries.end(), exp_upper,
|
||||
[n](const uint e) noexcept { return e - n; });
|
||||
exp_upper = Hold->mExpiries.begin();
|
||||
}
|
||||
std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; });
|
||||
const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex;
|
||||
std::transform(exp_upper, exp_lower+1, exp_upper,
|
||||
[n](const uint e) noexcept { return e - n; });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* Multichannel compression is linked via the absolute maximum of all
|
||||
* channels.
|
||||
*/
|
||||
void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer)
|
||||
void Compressor::linkChannels(const uint SamplesToDo,
|
||||
const al::span<const FloatBufferLine> OutBuffer)
|
||||
{
|
||||
const size_t numChans{Comp->mNumChans};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
std::fill(side_begin, side_begin+SamplesToDo, 0.0f);
|
||||
const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
|
||||
std::fill_n(sideChain.begin(), sideChain.size(), 0.0f);
|
||||
|
||||
auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void
|
||||
auto fill_max = [sideChain](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);
|
||||
const auto buffer = assume_aligned_span<16>(al::span{input});
|
||||
auto max_abs = [](const float s0, const float s1) noexcept -> float
|
||||
{ return std::max(s0, std::fabs(s1)); };
|
||||
std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(),
|
||||
max_abs);
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, fill_max);
|
||||
std::for_each(OutBuffer.begin(), OutBuffer.end(), fill_max);
|
||||
}
|
||||
|
||||
/* This calculates the squared crest factor of the control signal for the
|
||||
|
|
@ -121,60 +127,63 @@ void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLin
|
|||
* it uses an instantaneous squared peak detector and a squared RMS detector
|
||||
* both with 200ms release times.
|
||||
*/
|
||||
void CrestDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
void Compressor::crestDetector(const uint SamplesToDo)
|
||||
{
|
||||
const float a_crest{Comp->mCrestCoeff};
|
||||
float y2_peak{Comp->mLastPeakSq};
|
||||
float y2_rms{Comp->mLastRmsSq};
|
||||
const float a_crest{mCrestCoeff};
|
||||
float y2_peak{mLastPeakSq};
|
||||
float y2_rms{mLastRmsSq};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
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)};
|
||||
const float x2{std::clamp(x_abs*x_abs, 0.000001f, 1000000.0f)};
|
||||
|
||||
y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest));
|
||||
y2_peak = std::max(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);
|
||||
const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
|
||||
std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest);
|
||||
|
||||
Comp->mLastPeakSq = y2_peak;
|
||||
Comp->mLastRmsSq = y2_rms;
|
||||
mLastPeakSq = y2_peak;
|
||||
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)
|
||||
void Compressor::peakDetector(const uint SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
/* 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)); });
|
||||
/* Clamp the minimum amplitude to near-zero and convert to logarithmic. */
|
||||
const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
|
||||
std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(),
|
||||
[](float s) { return std::log(std::max(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)
|
||||
void Compressor::peakHoldDetector(const uint SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
SlidingHold *hold{Comp->mHold};
|
||||
SlidingHold *hold{mHold.get()};
|
||||
uint i{0};
|
||||
auto detect_peak = [&i,hold](const float x_abs) -> float
|
||||
{
|
||||
const float x_G{std::log(maxf(0.000001f, x_abs))};
|
||||
const float x_G{std::log(std::max(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);
|
||||
auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
|
||||
std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak);
|
||||
|
||||
ShiftSlidingHold(hold, SamplesToDo);
|
||||
}
|
||||
|
|
@ -184,46 +193,46 @@ void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo)
|
|||
* to knee width, attack/release times, make-up/post gain, and clipping
|
||||
* reduction.
|
||||
*/
|
||||
void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
||||
void Compressor::gainCompressor(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};
|
||||
const bool autoKnee{mAuto.Knee};
|
||||
const bool autoAttack{mAuto.Attack};
|
||||
const bool autoRelease{mAuto.Release};
|
||||
const bool autoPostGain{mAuto.PostGain};
|
||||
const bool autoDeclip{mAuto.Declip};
|
||||
const float threshold{mThreshold};
|
||||
const float slope{mSlope};
|
||||
const float attack{mAttack};
|
||||
const float release{mRelease};
|
||||
const float c_est{mGainEstimate};
|
||||
const float a_adp{mAdaptCoeff};
|
||||
auto lookAhead = mSideChain.cbegin() + mLookAhead;
|
||||
auto crestFactor = mCrestFactor.cbegin();
|
||||
float postGain{mPostGain};
|
||||
float knee{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};
|
||||
float y_1{mLastRelease};
|
||||
float y_L{mLastAttack};
|
||||
float c_dev{mLastGainDev};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(float &sideChain : al::span<float>{Comp->mSideChain, SamplesToDo})
|
||||
auto process = [&](const float input) -> float
|
||||
{
|
||||
if(autoKnee)
|
||||
knee = maxf(0.0f, 2.5f * (c_dev + c_est));
|
||||
knee = std::max(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 x_over{*(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) :
|
||||
(std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) :
|
||||
x_over};
|
||||
|
||||
const float y2_crest{*(crestFactor++)};
|
||||
|
|
@ -243,7 +252,7 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
|||
* above to compensate for the chained operating mode.
|
||||
*/
|
||||
const float x_L{-slope * y_G};
|
||||
y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel));
|
||||
y_1 = std::max(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
|
||||
|
|
@ -262,17 +271,19 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
|||
* same output level.
|
||||
*/
|
||||
if(autoDeclip)
|
||||
c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est);
|
||||
c_dev = std::max(c_dev, input - y_L - threshold - c_est);
|
||||
|
||||
postGain = -(c_dev + c_est);
|
||||
}
|
||||
|
||||
sideChain = std::exp(postGain - y_L);
|
||||
}
|
||||
return std::exp(postGain - y_L);
|
||||
};
|
||||
auto sideChain = al::span{mSideChain}.first(SamplesToDo);
|
||||
std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process);
|
||||
|
||||
Comp->mLastRelease = y_1;
|
||||
Comp->mLastAttack = y_L;
|
||||
Comp->mLastGainDev = c_dev;
|
||||
mLastRelease = y_1;
|
||||
mLastAttack = y_L;
|
||||
mLastGainDev = c_dev;
|
||||
}
|
||||
|
||||
/* Combined with the hold time, a look-ahead delay can improve handling of
|
||||
|
|
@ -280,36 +291,35 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
|
|||
* reaching the offending impulse. This is best used when operating as a
|
||||
* limiter.
|
||||
*/
|
||||
void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
||||
void Compressor::signalDelay(const uint SamplesToDo, const al::span<FloatBufferLine> OutBuffer)
|
||||
{
|
||||
const size_t numChans{Comp->mNumChans};
|
||||
const uint lookAhead{Comp->mLookAhead};
|
||||
const auto lookAhead = mLookAhead;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
ASSUME(lookAhead > 0);
|
||||
ASSUME(lookAhead < BufferLineSize);
|
||||
|
||||
for(size_t c{0};c < numChans;c++)
|
||||
auto delays = mDelay.begin();
|
||||
for(auto &buffer : OutBuffer)
|
||||
{
|
||||
float *inout{al::assume_aligned<16>(OutBuffer[c].data())};
|
||||
float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())};
|
||||
const auto inout = al::span{buffer}.first(SamplesToDo);
|
||||
const auto delaybuf = al::span{*(delays++)}.first(lookAhead);
|
||||
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if(SamplesToDo >= lookAhead) LIKELY
|
||||
if(SamplesToDo >= delaybuf.size()) LIKELY
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end);
|
||||
std::swap_ranges(inout, delay_end, delaybuf);
|
||||
const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size());
|
||||
const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end());
|
||||
std::swap_ranges(inout.begin(), delay_end, delaybuf.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(inout, inout_end, delaybuf);
|
||||
std::rotate(delaybuf, delay_start, delaybuf + lookAhead);
|
||||
auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin());
|
||||
std::rotate(delaybuf.begin(), delay_start, delaybuf.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // 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,
|
||||
|
|
@ -317,24 +327,12 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
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));
|
||||
const auto lookAhead = static_cast<uint>(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f,
|
||||
BufferLineSize-1.0f));
|
||||
const auto hold = static_cast<uint>(std::clamp(std::round(HoldTime*SampleRate), 0.0f,
|
||||
BufferLineSize-1.0f));
|
||||
|
||||
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)))};
|
||||
auto Comp = CompressorPtr{new Compressor{}};
|
||||
Comp->mNumChans = NumChans;
|
||||
Comp->mAuto.Knee = AutoKnee;
|
||||
Comp->mAuto.Attack = AutoAttack;
|
||||
|
|
@ -343,12 +341,12 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
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);
|
||||
Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb;
|
||||
Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb;
|
||||
Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f;
|
||||
Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb);
|
||||
Comp->mAttack = std::max(1.0f, AttackTime * SampleRate);
|
||||
Comp->mRelease = std::max(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
|
||||
|
|
@ -359,17 +357,18 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
|
||||
if(lookAhead > 0)
|
||||
{
|
||||
/* 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)
|
||||
{
|
||||
Comp->mHold = al::construct_at(reinterpret_cast<SlidingHold*>(Comp.get() + 1));
|
||||
Comp->mHold = std::make_unique<SlidingHold>();
|
||||
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->mDelay.resize(NumChans, FloatBufferLine{});
|
||||
}
|
||||
|
||||
Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms
|
||||
|
|
@ -379,15 +378,7 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
|
|||
return Comp;
|
||||
}
|
||||
|
||||
Compressor::~Compressor()
|
||||
{
|
||||
if(mHold)
|
||||
al::destroy_at(mHold);
|
||||
mHold = nullptr;
|
||||
if(mDelay)
|
||||
al::destroy_n(mDelay, mNumChans);
|
||||
mDelay = nullptr;
|
||||
}
|
||||
Compressor::~Compressor() = default;
|
||||
|
||||
|
||||
void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
||||
|
|
@ -395,45 +386,46 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
|||
const size_t numChans{mNumChans};
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
const auto output = al::span{OutBuffer, numChans};
|
||||
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; });
|
||||
const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo));
|
||||
std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(),
|
||||
[preGain](const float s) noexcept { return s * preGain; });
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_gain);
|
||||
std::for_each(output.begin(), output.end(), apply_gain);
|
||||
}
|
||||
|
||||
LinkChannels(this, SamplesToDo, OutBuffer);
|
||||
linkChannels(SamplesToDo, output);
|
||||
|
||||
if(mAuto.Attack || mAuto.Release)
|
||||
CrestDetector(this, SamplesToDo);
|
||||
crestDetector(SamplesToDo);
|
||||
|
||||
if(mHold)
|
||||
PeakHoldDetector(this, SamplesToDo);
|
||||
peakHoldDetector(SamplesToDo);
|
||||
else
|
||||
PeakDetector(this, SamplesToDo);
|
||||
peakDetector(SamplesToDo);
|
||||
|
||||
GainCompressor(this, SamplesToDo);
|
||||
gainCompressor(SamplesToDo);
|
||||
|
||||
if(mDelay)
|
||||
SignalDelay(this, SamplesToDo, OutBuffer);
|
||||
if(!mDelay.empty())
|
||||
signalDelay(SamplesToDo, output);
|
||||
|
||||
const float (&sideChain)[BufferLineSize*2] = mSideChain;
|
||||
auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void
|
||||
const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo));
|
||||
auto apply_comp = [gains](const FloatBufferSpan 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; });
|
||||
const auto buffer = assume_aligned_span<16>(input);
|
||||
std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(),
|
||||
std::multiplies{});
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
|
||||
std::for_each(output.begin(), output.end(), apply_comp);
|
||||
|
||||
auto side_begin = std::begin(mSideChain) + SamplesToDo;
|
||||
std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain));
|
||||
const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead);
|
||||
std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
#ifndef CORE_MASTERING_H
|
||||
#define CORE_MASTERING_H
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct SlidingHold;
|
||||
|
||||
|
|
@ -21,16 +25,17 @@ using uint = unsigned int;
|
|||
*
|
||||
* http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
|
||||
*/
|
||||
struct Compressor {
|
||||
class Compressor {
|
||||
size_t mNumChans{0u};
|
||||
|
||||
struct {
|
||||
struct AutoFlags {
|
||||
bool Knee : 1;
|
||||
bool Attack : 1;
|
||||
bool Release : 1;
|
||||
bool PostGain : 1;
|
||||
bool Declip : 1;
|
||||
} mAuto{};
|
||||
};
|
||||
AutoFlags mAuto{};
|
||||
|
||||
uint mLookAhead{0};
|
||||
|
||||
|
|
@ -44,11 +49,11 @@ struct Compressor {
|
|||
float mAttack{0.0f};
|
||||
float mRelease{0.0f};
|
||||
|
||||
alignas(16) float mSideChain[2*BufferLineSize]{};
|
||||
alignas(16) float mCrestFactor[BufferLineSize]{};
|
||||
alignas(16) std::array<float,BufferLineSize*2_uz> mSideChain{};
|
||||
alignas(16) std::array<float,BufferLineSize> mCrestFactor{};
|
||||
|
||||
SlidingHold *mHold{nullptr};
|
||||
FloatBufferLine *mDelay{nullptr};
|
||||
std::unique_ptr<SlidingHold> mHold;
|
||||
al::vector<FloatBufferLine,16> mDelay;
|
||||
|
||||
float mCrestCoeff{0.0f};
|
||||
float mGainEstimate{0.0f};
|
||||
|
|
@ -60,12 +65,19 @@ struct Compressor {
|
|||
float mLastAttack{0.0f};
|
||||
float mLastGainDev{0.0f};
|
||||
|
||||
Compressor() = default;
|
||||
|
||||
void linkChannels(const uint SamplesToDo, const al::span<const FloatBufferLine> OutBuffer);
|
||||
void crestDetector(const uint SamplesToDo);
|
||||
void peakDetector(const uint SamplesToDo);
|
||||
void peakHoldDetector(const uint SamplesToDo);
|
||||
void gainCompressor(const uint SamplesToDo);
|
||||
void signalDelay(const uint SamplesToDo, const al::span<FloatBufferLine> OutBuffer);
|
||||
|
||||
public:
|
||||
~Compressor();
|
||||
void process(const uint SamplesToDo, FloatBufferLine *OutBuffer);
|
||||
int getLookAhead() const noexcept { return static_cast<int>(mLookAhead); }
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
[[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; }
|
||||
|
||||
/**
|
||||
* The compressor is initialized with the following settings:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#include "mixer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "devformat.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "device.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
|
|
@ -82,14 +84,13 @@ std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, c
|
|||
return coeffs;
|
||||
}
|
||||
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MaxAmbiChannels> gains)
|
||||
void ComputePanGains(const MixParams *mix, const al::span<const float,MaxAmbiChannels> coeffs,
|
||||
const float ingain, const al::span<float,MaxAmbiChannels> gains)
|
||||
{
|
||||
auto ambimap = mix->AmbiMap.cbegin();
|
||||
auto ambimap = al::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size());
|
||||
|
||||
auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(),
|
||||
auto iter = std::transform(ambimap.begin(), ambimap.end(), gains.begin(),
|
||||
[coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float
|
||||
{ return chanmap.Scale * coeffs[chanmap.Index] * ingain; }
|
||||
);
|
||||
{ return chanmap.Scale * coeffs[chanmap.Index] * ingain; });
|
||||
std::fill(iter, gains.end(), 0.0f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,34 +3,32 @@
|
|||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <stddef.h>
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
|
||||
#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);
|
||||
const al::span<FloatBufferLine> OutBuffer, const al::span<float> CurrentGains,
|
||||
const al::span<const float> TargetGains, const std::size_t Counter, const std::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)
|
||||
const al::span<FloatBufferLine> OutBuffer, const al::span<float> CurrentGains,
|
||||
const al::span<const float> TargetGains, const std::size_t Counter, const std::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);
|
||||
using MixerOneFunc = void(*)(const al::span<const float> InSamples,const al::span<float> OutBuffer,
|
||||
float &CurrentGain, const float TargetGain, const std::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)
|
||||
inline void MixSamples(const al::span<const float> InSamples, const al::span<float> OutBuffer,
|
||||
float &CurrentGain, const float TargetGain, const std::size_t Counter)
|
||||
{ MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); }
|
||||
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, c
|
|||
* 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],
|
||||
inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const al::span<const float,3> dir,
|
||||
const float spread)
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
|
|
@ -71,7 +69,7 @@ inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[
|
|||
* 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])
|
||||
constexpr std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const al::span<const float,3> dir)
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]);
|
||||
|
|
@ -103,7 +101,7 @@ inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth,
|
|||
* 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);
|
||||
void ComputePanGains(const MixParams *mix, const al::span<const float,MaxAmbiChannels> coeffs,
|
||||
const float ingain, const al::span<float,MaxAmbiChannels> gains);
|
||||
|
||||
#endif /* CORE_MIXER_H */
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
#define CORE_MIXER_DEFS_H
|
||||
|
||||
#include <array>
|
||||
#include <stdlib.h>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "core/cubic_defs.h"
|
||||
|
||||
struct CubicCoefficients;
|
||||
struct HrtfChannelState;
|
||||
struct HrtfFilter;
|
||||
struct MixHrtfFilter;
|
||||
|
|
@ -17,18 +19,19 @@ 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};
|
||||
inline constexpr int MixerFracBits{16};
|
||||
inline constexpr int MixerFracOne{1 << MixerFracBits};
|
||||
inline constexpr int MixerFracMask{MixerFracOne - 1};
|
||||
inline constexpr int MixerFracHalf{MixerFracOne >> 1};
|
||||
|
||||
constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
|
||||
|
||||
enum class Resampler : uint8_t {
|
||||
enum class Resampler : std::uint8_t {
|
||||
Point,
|
||||
Linear,
|
||||
Cubic,
|
||||
Spline,
|
||||
Gaussian,
|
||||
FastBSinc12,
|
||||
BSinc12,
|
||||
FastBSinc24,
|
||||
|
|
@ -49,56 +52,59 @@ struct BsincState {
|
|||
* delta coefficients. Starting at phase index 0, each subsequent phase
|
||||
* index follows contiguously.
|
||||
*/
|
||||
const float *filter;
|
||||
al::span<const float> filter;
|
||||
};
|
||||
|
||||
struct CubicState {
|
||||
/* Filter coefficients, and coefficient deltas. Starting at phase index 0,
|
||||
* each subsequent phase index follows contiguously.
|
||||
*/
|
||||
const CubicCoefficients *filter;
|
||||
al::span<const CubicCoefficients,CubicPhaseCount> filter;
|
||||
CubicState(al::span<const CubicCoefficients,CubicPhaseCount> f) : filter{f} { }
|
||||
};
|
||||
|
||||
union InterpState {
|
||||
CubicState cubic;
|
||||
BsincState bsinc;
|
||||
};
|
||||
using InterpState = std::variant<std::monostate,CubicState,BsincState>;
|
||||
|
||||
using ResamplerFunc = void(*)(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
using ResamplerFunc = void(*)(const InterpState *state, const al::span<const float> 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,
|
||||
void Resample_(const InterpState *state, const al::span<const float> 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);
|
||||
const al::span<float> CurrentGains, const al::span<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);
|
||||
void Mix_(const al::span<const float> InSamples, const al::span<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);
|
||||
void MixHrtf_(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo);
|
||||
template<typename InstTag>
|
||||
void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize);
|
||||
void MixHrtfBlend_(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
|
||||
const size_t SamplesToDo);
|
||||
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);
|
||||
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
||||
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
|
||||
const size_t IrSize, const size_t SamplesToDo);
|
||||
|
||||
/* Vectorized resampler helpers */
|
||||
template<size_t N>
|
||||
inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N])
|
||||
constexpr void InitPosArrays(uint pos, uint frac, const uint increment,
|
||||
const al::span<uint,N> frac_arr, const al::span<uint,N> pos_arr)
|
||||
{
|
||||
pos_arr[0] = 0;
|
||||
static_assert(pos_arr.size() == frac_arr.size());
|
||||
pos_arr[0] = pos;
|
||||
frac_arr[0] = frac;
|
||||
for(size_t i{1};i < N;i++)
|
||||
for(size_t i{1};i < pos_arr.size();++i)
|
||||
{
|
||||
const uint frac_tmp{frac_arr[i-1] + increment};
|
||||
pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits);
|
||||
|
|
|
|||
|
|
@ -4,21 +4,23 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfdefs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize,
|
||||
using ApplyCoeffsT = void(const al::span<float2> 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)
|
||||
inline void MixHrtfBase(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const size_t IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
|
||||
const ConstHrirSpan Coeffs{hrtfparams->Coeffs};
|
||||
const float gainstep{hrtfparams->GainStep};
|
||||
|
|
@ -27,26 +29,28 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c
|
|||
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)
|
||||
for(size_t i{0u};i < SamplesToDo;++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);
|
||||
ApplyCoeffs(AccumSamples.subspan(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)
|
||||
inline void MixHrtfBlendBase(const al::span<const float> InSamples,
|
||||
const al::span<float2> AccumSamples, const size_t IrSize, const HrtfFilter *oldparams,
|
||||
const MixHrtfFilter *newparams, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
|
||||
const ConstHrirSpan OldCoeffs{oldparams->Coeffs};
|
||||
const float oldGainStep{oldparams->Gain / static_cast<float>(BufferSize)};
|
||||
const float oldGainStep{oldparams->Gain / static_cast<float>(SamplesToDo)};
|
||||
const ConstHrirSpan NewCoeffs{newparams->Coeffs};
|
||||
const float newGainStep{newparams->GainStep};
|
||||
|
||||
|
|
@ -54,29 +58,29 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
{
|
||||
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)
|
||||
auto stepcount = static_cast<float>(SamplesToDo);
|
||||
for(size_t i{0u};i < SamplesToDo;++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);
|
||||
ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right);
|
||||
|
||||
stepcount -= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if(newGainStep*static_cast<float>(BufferSize) > GainSilenceThreshold) LIKELY
|
||||
if(newGainStep*static_cast<float>(SamplesToDo) > 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)
|
||||
for(size_t i{1u};i < SamplesToDo;++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);
|
||||
ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right);
|
||||
|
||||
stepcount += 1.0f;
|
||||
}
|
||||
|
|
@ -85,45 +89,52 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
|
||||
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)
|
||||
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
||||
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChannelState,
|
||||
const size_t IrSize, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(BufferSize > 0);
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
assert(ChannelState.size() == InSamples.size());
|
||||
|
||||
auto ChanState = ChannelState.begin();
|
||||
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->mSplitter.processHfScale(al::span{input}.first(SamplesToDo), 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)
|
||||
for(size_t i{0u};i < SamplesToDo;++i)
|
||||
{
|
||||
const float insample{tempbuf[i]};
|
||||
ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample);
|
||||
const float insample{TempBuf[i]};
|
||||
ApplyCoeffs(AccumSamples.subspan(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];
|
||||
const auto left = al::span{al::assume_aligned<16>(LeftOut.data()), SamplesToDo};
|
||||
std::transform(left.cbegin(), left.cend(), AccumSamples.cbegin(), left.begin(),
|
||||
[](const float sample, const float2 &accum) noexcept -> float
|
||||
{ return sample + accum[0]; });
|
||||
const auto right = al::span{al::assume_aligned<16>(RightOut.data()), SamplesToDo};
|
||||
std::transform(right.cbegin(), right.cend(), AccumSamples.cbegin(), right.begin(),
|
||||
[](const float sample, const float2 &accum) noexcept -> float
|
||||
{ return sample + accum[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{});
|
||||
const auto accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength);
|
||||
auto accum_iter = std::copy(accum_inprog.cbegin(), accum_inprog.cend(), AccumSamples.begin());
|
||||
std::fill_n(accum_iter, SamplesToDo, float2{});
|
||||
}
|
||||
|
||||
#endif /* CORE_MIXER_HRTFBASE_H */
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <variant>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "core/mixer/hrtfdefs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
struct CTag;
|
||||
struct PointTag;
|
||||
|
|
@ -28,191 +35,246 @@ 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)
|
||||
using SamplerNST = float(const al::span<const float>, const size_t, const uint) noexcept;
|
||||
|
||||
template<typename T>
|
||||
using SamplerT = float(const T&,const al::span<const float>,const size_t,const uint) noexcept;
|
||||
|
||||
[[nodiscard]] constexpr
|
||||
auto do_point(const al::span<const float> vals, const size_t pos, const uint) noexcept -> float
|
||||
{ return vals[pos]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto do_lerp(const al::span<const float> vals, const size_t pos, const uint frac) noexcept -> float
|
||||
{ return lerpf(vals[pos+0], vals[pos+1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
[[nodiscard]] constexpr
|
||||
auto do_cubic(const CubicState &istate, const al::span<const float> vals, const size_t pos,
|
||||
const uint frac) noexcept -> float
|
||||
{
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
|
||||
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)};
|
||||
const auto fil = al::span{istate.filter[pi].mCoeffs};
|
||||
const auto phd = al::span{istate.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];
|
||||
return (fil[0] + pf*phd[0])*vals[pos+0] + (fil[1] + pf*phd[1])*vals[pos+1]
|
||||
+ (fil[2] + pf*phd[2])*vals[pos+2] + (fil[3] + pf*phd[3])*vals[pos+3];
|
||||
}
|
||||
inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
[[nodiscard]] constexpr
|
||||
auto do_fastbsinc(const BsincState &bsinc, const al::span<const float> vals, const size_t pos,
|
||||
const uint frac) noexcept -> float
|
||||
{
|
||||
const size_t m{istate.bsinc.m};
|
||||
const size_t m{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> BsincPhaseDiffBits};
|
||||
const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
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};
|
||||
const auto fil = bsinc.filter.subspan(2_uz*pi*m);
|
||||
const auto phd = fil.subspan(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[pos+j_f];
|
||||
return r;
|
||||
}
|
||||
[[nodiscard]] constexpr
|
||||
auto do_bsinc(const BsincState &bsinc, const al::span<const float> vals, const size_t pos,
|
||||
const uint frac) noexcept -> float
|
||||
{
|
||||
const size_t m{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
const float pf{static_cast<float>(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)};
|
||||
|
||||
const auto fil = bsinc.filter.subspan(2_uz*pi*m);
|
||||
const auto phd = fil.subspan(m);
|
||||
const auto scd = fil.subspan(BSincPhaseCount*2_uz*m);
|
||||
const auto spd = scd.subspan(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];
|
||||
for(size_t j_f{0};j_f < m;++j_f)
|
||||
r += (fil[j_f] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+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)
|
||||
template<SamplerNST Sampler>
|
||||
void DoResample(const al::span<const float> src, uint frac, const uint increment,
|
||||
const al::span<float> dst)
|
||||
{
|
||||
const InterpState istate{*state};
|
||||
ASSUME(frac < MixerFracOne);
|
||||
for(float &out : dst)
|
||||
size_t pos{0};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float
|
||||
{
|
||||
out = Sampler(istate, src, frac);
|
||||
|
||||
const float output{Sampler(src, pos, frac)};
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
template<typename U, SamplerT<U> Sampler>
|
||||
void DoResample(const U istate, const al::span<const float> src, uint frac, const uint increment,
|
||||
const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
size_t pos{0};
|
||||
std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> float
|
||||
{
|
||||
const float output{Sampler(istate, src, pos, frac)};
|
||||
frac += increment;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(const al::span<float2> Values, const size_t IrSize,
|
||||
const ConstHrirSpan Coeffs, const float left, const float right) noexcept
|
||||
{
|
||||
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;
|
||||
}
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
|
||||
auto mix_impulse = [left,right](const float2 &value, const float2 &coeff) noexcept -> float2
|
||||
{ return float2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; };
|
||||
std::transform(Values.cbegin(), Values.cbegin()+ptrdiff_t(IrSize), Coeffs.cbegin(),
|
||||
Values.begin(), mix_impulse);
|
||||
}
|
||||
|
||||
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,
|
||||
force_inline void MixLine(al::span<const float> InSamples, const al::span<float> dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len,
|
||||
size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
const float step{(TargetGain-CurrentGain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
auto output = dst.begin();
|
||||
if(std::abs(step) > std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
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;
|
||||
auto input = InSamples.first(fade_len);
|
||||
InSamples = InSamples.subspan(fade_len);
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
const float gain{CurrentGain};
|
||||
float step_count{0.0f};
|
||||
output = std::transform(input.begin(), input.end(), output, output,
|
||||
[gain,step,&step_count](const float in, float out) noexcept -> float
|
||||
{
|
||||
out += in * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
return out;
|
||||
});
|
||||
|
||||
if(fade_len < Counter)
|
||||
{
|
||||
CurrentGain = gain + step*step_count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
CurrentGain = TargetGain;
|
||||
|
||||
if(!(std::abs(TargetGain) > GainSilenceThreshold))
|
||||
return;
|
||||
for(;pos != InSamples.size();++pos)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
|
||||
std::transform(InSamples.begin(), InSamples.end(), output, output,
|
||||
[TargetGain](const float in, const float out) noexcept -> float
|
||||
{ return out + in*TargetGain; });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<PointTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
void Resample_<PointTag,CTag>(const InterpState*, const al::span<const float> src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_point>(state, src, frac, increment, dst); }
|
||||
{ DoResample<do_point>(src.subspan(MaxResamplerEdge), frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
void Resample_<LerpTag,CTag>(const InterpState*, const al::span<const float> src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_lerp>(state, src, frac, increment, dst); }
|
||||
{ DoResample<do_lerp>(src.subspan(MaxResamplerEdge), frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
void Resample_<CubicTag,CTag>(const InterpState *state, const al::span<const float> src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_cubic>(state, src-1, frac, increment, dst); }
|
||||
{
|
||||
DoResample<CubicState,do_cubic>(std::get<CubicState>(*state), src.subspan(MaxResamplerEdge-1),
|
||||
frac, increment, dst);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
void Resample_<FastBSincTag,CTag>(const InterpState *state, const al::span<const float> src,
|
||||
uint frac, const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const auto istate = std::get<BsincState>(*state);
|
||||
ASSUME(istate.l <= MaxResamplerEdge);
|
||||
DoResample<BsincState,do_fastbsinc>(istate, src.subspan(MaxResamplerEdge-istate.l), frac,
|
||||
increment, dst);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,CTag>(const InterpState *state, const al::span<const float> 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); }
|
||||
{
|
||||
const auto istate = std::get<BsincState>(*state);
|
||||
ASSUME(istate.l <= MaxResamplerEdge);
|
||||
DoResample<BsincState,do_bsinc>(istate, src.subspan(MaxResamplerEdge-istate.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); }
|
||||
void MixHrtf_<CTag>(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<CTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
void MixHrtfBlend_<CTag>(const al::span<const float> InSamples,const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
|
||||
const size_t SamplesToDo)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
SamplesToDo);
|
||||
}
|
||||
|
||||
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)
|
||||
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
||||
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
|
||||
const size_t IrSize, const size_t SamplesToDo)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
IrSize, SamplesToDo);
|
||||
}
|
||||
|
||||
|
||||
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 al::span<float> CurrentGains, const al::span<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 fade_len = std::min(Counter, InSamples.size());
|
||||
|
||||
auto curgains = CurrentGains.begin();
|
||||
auto targetgains = TargetGains.cbegin();
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, Counter);
|
||||
MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta,
|
||||
fade_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<CTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
void Mix_<CTag>(const al::span<const float> InSamples, const al::span<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 fade_len = std::min(Counter, InSamples.size());
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain,
|
||||
TargetGain, delta, min_len, Counter);
|
||||
MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,22 @@
|
|||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <variant>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "core/mixer/hrtfdefs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
struct NEONTag;
|
||||
struct LerpTag;
|
||||
|
|
@ -22,6 +30,8 @@ struct FastBSincTag;
|
|||
#pragma GCC target("fpu=neon")
|
||||
#endif
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
|
|
@ -32,6 +42,19 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
|||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
force_inline
|
||||
void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) noexcept
|
||||
{
|
||||
float32x4x2_t t0_{vzipq_f32(x0, x2)};
|
||||
float32x4x2_t t1_{vzipq_f32(x1, x3)};
|
||||
float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])};
|
||||
float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])};
|
||||
x0 = u0_.val[0];
|
||||
x1 = u0_.val[1];
|
||||
x2 = u1_.val[0];
|
||||
x3 = u1_.val[1];
|
||||
}
|
||||
|
||||
inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(l0)};
|
||||
|
|
@ -41,60 +64,59 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
|||
return ret;
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
inline void ApplyCoeffs(const al::span<float2> 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)
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
|
||||
auto dup_samples = [left,right]() -> float32x4_t
|
||||
{
|
||||
float32x4_t vals = vld1q_f32(&Values[c][0]);
|
||||
float32x4_t coefs = vld1q_f32(&Coeffs[c][0]);
|
||||
float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)};
|
||||
return vcombine_f32(leftright2, leftright2);
|
||||
};
|
||||
const auto leftright4 = dup_samples();
|
||||
const auto count4 = size_t{(IrSize+1) >> 1};
|
||||
|
||||
vals = vmlaq_f32(vals, coefs, leftright4);
|
||||
|
||||
vst1q_f32(&Values[c][0], vals);
|
||||
}
|
||||
const auto vals4 = al::span{reinterpret_cast<float32x4_t*>(Values[0].data()), count4};
|
||||
const auto coeffs4 = al::span{reinterpret_cast<const float32x4_t*>(Coeffs[0].data()), count4};
|
||||
std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(),
|
||||
[leftright4](const float32x4_t &val, const float32x4_t &coeff) -> float32x4_t
|
||||
{ return vmlaq_f32(val, coeff, leftright4); });
|
||||
}
|
||||
|
||||
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)
|
||||
force_inline void MixLine(const al::span<const float> InSamples, const al::span<float> dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len,
|
||||
const size_t realign_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
const auto step = float{(TargetGain-CurrentGain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
auto pos = size_t{0};
|
||||
if(std::abs(step) > std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
float step_count{0.0f};
|
||||
const auto gain = float{CurrentGain};
|
||||
auto step_count = float{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
if(const size_t todo{fade_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);
|
||||
const auto four4 = vdupq_n_f32(4.0f);
|
||||
const auto step4 = vdupq_n_f32(step);
|
||||
const auto gain4 = vdupq_n_f32(gain);
|
||||
auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f);
|
||||
|
||||
const auto in4 = al::span{reinterpret_cast<const float32x4_t*>(InSamples.data()),
|
||||
InSamples.size()/4}.first(todo);
|
||||
const auto out4 = al::span{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
|
||||
std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(),
|
||||
[gain4,step4,four4,&step_count4](const float32x4_t val4, float32x4_t dry4)
|
||||
{
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
|
||||
step_count4 = vaddq_f32(step_count4, four4);
|
||||
return dry4;
|
||||
});
|
||||
pos += in4.size()*4;
|
||||
|
||||
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.
|
||||
|
|
@ -102,152 +124,242 @@ force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT
|
|||
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)
|
||||
if(const size_t leftover{fade_len&3})
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
const auto in = InSamples.subspan(pos, leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[gain,step,&step_count](const float val, float dry) noexcept -> float
|
||||
{
|
||||
dry += val * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
return dry;
|
||||
});
|
||||
pos += leftover;
|
||||
}
|
||||
if(pos < Counter)
|
||||
{
|
||||
CurrentGain = gain + step*step_count;
|
||||
return;
|
||||
}
|
||||
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(const size_t leftover{realign_len&3})
|
||||
{
|
||||
const auto in = InSamples.subspan(pos, leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
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);
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[TargetGain](const float val, const float dry) noexcept -> float
|
||||
{ return dry + val*TargetGain; });
|
||||
pos += leftover;
|
||||
}
|
||||
}
|
||||
CurrentGain = TargetGain;
|
||||
|
||||
if(!(std::abs(TargetGain) > GainSilenceThreshold))
|
||||
return;
|
||||
if(const size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const auto in4 = al::span{reinterpret_cast<const float32x4_t*>(InSamples.data()),
|
||||
InSamples.size()/4}.last(todo);
|
||||
const auto out = dst.subspan(pos);
|
||||
const auto out4 = al::span{reinterpret_cast<float32x4_t*>(out.data()), out.size()/4};
|
||||
|
||||
const auto gain4 = vdupq_n_f32(TargetGain);
|
||||
std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(),
|
||||
[gain4](const float32x4_t val4, const float32x4_t dry4) -> float32x4_t
|
||||
{ return vmlaq_f32(dry4, val4, gain4); });
|
||||
pos += in4.size()*4;
|
||||
}
|
||||
if(const size_t leftover{(InSamples.size()-pos)&3})
|
||||
{
|
||||
const auto in = InSamples.last(leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[TargetGain](const float val, const float dry) noexcept -> float
|
||||
{ return dry + val*TargetGain; });
|
||||
}
|
||||
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,
|
||||
void Resample_<LerpTag,NEONTag>(const InterpState*, const al::span<const float> 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 uint32x4_t increment4 = vdupq_n_u32(increment*4u);
|
||||
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;
|
||||
const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask);
|
||||
|
||||
InitPosArrays(frac, increment, frac_, pos_);
|
||||
frac4 = vld1q_s32(reinterpret_cast<int*>(frac_));
|
||||
pos4 = vld1q_s32(reinterpret_cast<int*>(pos_));
|
||||
alignas(16) std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_});
|
||||
uint32x4_t frac4 = vld1q_u32(frac_.data());
|
||||
uint32x4_t pos4 = vld1q_u32(pos_.data());
|
||||
|
||||
auto dst_iter = dst.begin();
|
||||
for(size_t todo{dst.size()>>2};todo;--todo)
|
||||
auto vecout = al::span{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t
|
||||
{
|
||||
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 uint pos0{vgetq_lane_u32(pos4, 0)};
|
||||
const uint pos1{vgetq_lane_u32(pos4, 1)};
|
||||
const uint pos2{vgetq_lane_u32(pos4, 2)};
|
||||
const uint pos3{vgetq_lane_u32(pos4, 3)};
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
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])};
|
||||
const float32x4_t val2{set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])};
|
||||
|
||||
/* 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 mu{vmulq_f32(vcvtq_f32_u32(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);
|
||||
}
|
||||
frac4 = vaddq_u32(frac4, increment4);
|
||||
pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits));
|
||||
frac4 = vandq_u32(frac4, fracMask4);
|
||||
return out;
|
||||
});
|
||||
|
||||
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));
|
||||
auto pos = size_t{vgetq_lane_u32(pos4, 0)};
|
||||
frac = vgetq_lane_u32(frac4, 0);
|
||||
|
||||
do {
|
||||
*(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
|
||||
const auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]
|
||||
{
|
||||
const float output{lerpf(src[pos+0], src[pos+1],
|
||||
static_cast<float>(frac) * (1.0f/MixerFracOne))};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
void Resample_<CubicTag,NEONTag>(const InterpState *state, const al::span<const float> 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);
|
||||
const auto filter = std::get<CubicState>(*state).filter;
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
const uint32x4_t increment4{vdupq_n_u32(increment*4u)};
|
||||
const uint32x4_t fracMask4{vdupq_n_u32(MixerFracMask)};
|
||||
const float32x4_t fracDiffOne4{vdupq_n_f32(1.0f/CubicPhaseDiffOne)};
|
||||
const uint32x4_t fracDiffMask4{vdupq_n_u32(CubicPhaseDiffMask)};
|
||||
|
||||
alignas(16) std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_});
|
||||
uint32x4_t frac4{vld1q_u32(frac_.data())};
|
||||
uint32x4_t pos4{vld1q_u32(pos_.data())};
|
||||
|
||||
auto vecout = al::span{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
const uint pos0{vgetq_lane_u32(pos4, 0)};
|
||||
const uint pos1{vgetq_lane_u32(pos4, 1)};
|
||||
const uint pos2{vgetq_lane_u32(pos4, 2)};
|
||||
const uint pos3{vgetq_lane_u32(pos4, 3)};
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
const float32x4_t val0{vld1q_f32(&src[pos0])};
|
||||
const float32x4_t val1{vld1q_f32(&src[pos1])};
|
||||
const float32x4_t val2{vld1q_f32(&src[pos2])};
|
||||
const float32x4_t val3{vld1q_f32(&src[pos3])};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)};
|
||||
const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount);
|
||||
const uint pi1{vgetq_lane_u32(pi4, 1)}; ASSUME(pi1 < CubicPhaseCount);
|
||||
const uint pi2{vgetq_lane_u32(pi4, 2)}; ASSUME(pi2 < CubicPhaseCount);
|
||||
const uint pi3{vgetq_lane_u32(pi4, 3)}; ASSUME(pi3 < CubicPhaseCount);
|
||||
|
||||
/* 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))};
|
||||
const float32x4_t pf4{vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)),
|
||||
fracDiffOne4)};
|
||||
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
float32x4_t r0{vmulq_f32(val0,
|
||||
vmlaq_f32(vld1q_f32(filter[pi0].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 0),
|
||||
vld1q_f32(filter[pi0].mDeltas.data())))};
|
||||
float32x4_t r1{vmulq_f32(val1,
|
||||
vmlaq_f32(vld1q_f32(filter[pi1].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 1),
|
||||
vld1q_f32(filter[pi1].mDeltas.data())))};
|
||||
float32x4_t r2{vmulq_f32(val2,
|
||||
vmlaq_f32(vld1q_f32(filter[pi2].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 0),
|
||||
vld1q_f32(filter[pi2].mDeltas.data())))};
|
||||
float32x4_t r3{vmulq_f32(val3,
|
||||
vmlaq_f32(vld1q_f32(filter[pi3].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 1),
|
||||
vld1q_f32(filter[pi3].mDeltas.data())))};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
vtranspose4(r0, r1, r2, r3);
|
||||
r0 = vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3));
|
||||
|
||||
frac4 = vaddq_u32(frac4, increment4);
|
||||
pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits));
|
||||
frac4 = vandq_u32(frac4, fracMask4);
|
||||
return r0;
|
||||
});
|
||||
|
||||
if(const size_t todo{dst.size()&3})
|
||||
{
|
||||
auto pos = size_t{vgetq_lane_u32(pos4, 0)};
|
||||
frac = vgetq_lane_u32(frac4, 0);
|
||||
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter]
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
|
||||
const float32x4_t f4{vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4,
|
||||
vld1q_f32(filter[pi].mDeltas.data()))};
|
||||
float32x4_t r4{vmulq_f32(f4, vld1q_f32(&src[pos]))};
|
||||
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)};
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
void Resample_<BSincTag,NEONTag>(const InterpState *state, const al::span<const float> 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};
|
||||
const auto &bsinc = std::get<BsincState>(*state);
|
||||
const auto sf4 = vdupq_n_f32(bsinc.sf);
|
||||
const auto m = size_t{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m);
|
||||
|
||||
ASSUME(bsinc.l <= MaxResamplerEdge);
|
||||
auto pos = size_t{MaxResamplerEdge-bsinc.l};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
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};
|
||||
const auto fil = filter.subspan(2_uz*pi*m);
|
||||
const auto phd = fil.subspan(m);
|
||||
const auto scd = fil.subspan(2_uz*BSincPhaseCount*m);
|
||||
const auto spd = scd.subspan(m);
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -257,41 +369,46 @@ void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *RESTRICT
|
|||
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]));
|
||||
r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+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);
|
||||
const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const al::span<const float> 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};
|
||||
const auto &bsinc = std::get<BsincState>(*state);
|
||||
const auto m = size_t{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
const auto filter = bsinc.filter.first(2_uz*BSincPhaseCount*m);
|
||||
|
||||
ASSUME(bsinc.l <= MaxResamplerEdge);
|
||||
auto pos = size_t{MaxResamplerEdge-bsinc.l};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,m,filter]() -> float
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
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};
|
||||
const auto fil = filter.subspan(2_uz*pi*m);
|
||||
const auto phd = fil.subspan(m);
|
||||
size_t td{m >> 2};
|
||||
size_t j{0u};
|
||||
|
||||
|
|
@ -299,64 +416,69 @@ void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *REST
|
|||
/* 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]));
|
||||
r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+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);
|
||||
const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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); }
|
||||
void MixHrtf_<NEONTag>(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
void MixHrtfBlend_<NEONTag>(const al::span<const float> InSamples,
|
||||
const al::span<float2> AccumSamples, const uint IrSize, const HrtfFilter *oldparams,
|
||||
const MixHrtfFilter *newparams, const size_t SamplesToDo)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
SamplesToDo);
|
||||
}
|
||||
|
||||
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)
|
||||
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
||||
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
|
||||
const size_t IrSize, const size_t SamplesToDo)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
IrSize, SamplesToDo);
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples,const al::span<FloatBufferLine> OutBuffer,
|
||||
const al::span<float> CurrentGains, const al::span<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;
|
||||
const auto fade_len = std::min(Counter, InSamples.size());
|
||||
const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len;
|
||||
|
||||
auto curgains = CurrentGains.begin();
|
||||
auto targetgains = TargetGains.cbegin();
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta,
|
||||
fade_len, realign_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<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;
|
||||
const auto fade_len = std::min(Counter, InSamples.size());
|
||||
const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <mmintrin.h>
|
||||
#include <xmmintrin.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <variant>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "core/mixer/hrtfdefs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
struct SSETag;
|
||||
struct CubicTag;
|
||||
|
|
@ -31,42 +41,48 @@ 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))
|
||||
force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept
|
||||
{ return _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)
|
||||
inline void ApplyCoeffs(const al::span<float2> 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);
|
||||
ASSUME(IrSize <= HrirLength);
|
||||
const auto lrlr = _mm_setr_ps(left, right, left, right);
|
||||
/* Round up the IR size to a multiple of 2 for SIMD (2 IRs for 2 channels
|
||||
* is 4 floats), to avoid cutting the last sample for odd IR counts. The
|
||||
* underlying HRIR is a fixed-size multiple of 2, any extra samples are
|
||||
* either 0 (silence) or more IR samples that get applied for "free".
|
||||
*/
|
||||
const auto count4 = size_t{(IrSize+1) >> 1};
|
||||
|
||||
/* 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))
|
||||
if(!(reinterpret_cast<uintptr_t>(Values.data())&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);
|
||||
}
|
||||
const auto vals4 = al::span{reinterpret_cast<__m128*>(Values[0].data()), count4};
|
||||
const auto coeffs4 = al::span{reinterpret_cast<const __m128*>(Coeffs[0].data()), count4};
|
||||
|
||||
std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(),
|
||||
[lrlr](const __m128 &val, const __m128 &coeff) -> __m128
|
||||
{ return vmadd(val, coeff, lrlr); });
|
||||
}
|
||||
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);
|
||||
auto coeffs = _mm_load_ps(Coeffs[0].data());
|
||||
auto vals = _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()));
|
||||
auto 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 td{count4 - 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);
|
||||
const auto 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);
|
||||
|
|
@ -80,37 +96,38 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
force_inline void MixLine(const al::span<const float> InSamples, const al::span<float> dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len,
|
||||
const size_t realign_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
const auto step = float{(TargetGain-CurrentGain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
if(std::abs(step) > std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
float step_count{0.0f};
|
||||
const auto gain = float{CurrentGain};
|
||||
auto step_count = float{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
if(const size_t todo{fade_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])};
|
||||
const auto four4 = _mm_set1_ps(4.0f);
|
||||
const auto step4 = _mm_set1_ps(step);
|
||||
const auto gain4 = _mm_set1_ps(gain);
|
||||
auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f);
|
||||
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
|
||||
const auto in4 = al::span{reinterpret_cast<const __m128*>(InSamples.data()),
|
||||
InSamples.size()/4}.first(todo);
|
||||
const auto out4 = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
|
||||
std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(),
|
||||
[gain4,step4,four4,&step_count4](const __m128 val4, __m128 dry4) -> __m128
|
||||
{
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4));
|
||||
step_count4 = _mm_add_ps(step_count4, four4);
|
||||
return dry4;
|
||||
});
|
||||
pos += in4.size()*4;
|
||||
|
||||
_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.
|
||||
|
|
@ -118,210 +135,252 @@ force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT
|
|||
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)
|
||||
if(const size_t leftover{fade_len&3})
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
const auto in = InSamples.subspan(pos, leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[gain,step,&step_count](const float val, float dry) noexcept -> float
|
||||
{
|
||||
dry += val * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
return dry;
|
||||
});
|
||||
pos += leftover;
|
||||
}
|
||||
if(pos < Counter)
|
||||
{
|
||||
CurrentGain = gain + step*step_count;
|
||||
return;
|
||||
}
|
||||
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(const size_t leftover{realign_len&3})
|
||||
{
|
||||
const auto in = InSamples.subspan(pos, leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[TargetGain](const float val, const float dry) noexcept -> float
|
||||
{ return dry + val*TargetGain; });
|
||||
pos += leftover;
|
||||
}
|
||||
}
|
||||
CurrentGain = TargetGain;
|
||||
|
||||
if(!(std::abs(TargetGain) > 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);
|
||||
const auto in4 = al::span{reinterpret_cast<const __m128*>(InSamples.data()),
|
||||
InSamples.size()/4}.last(todo);
|
||||
const auto out = dst.subspan(pos);
|
||||
const auto out4 = al::span{reinterpret_cast<__m128*>(out.data()), out.size()/4};
|
||||
|
||||
const auto gain4 = _mm_set1_ps(TargetGain);
|
||||
std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(),
|
||||
[gain4](const __m128 val4, const __m128 dry4) -> __m128
|
||||
{ return vmadd(dry4, val4, gain4); });
|
||||
pos += in4.size()*4;
|
||||
}
|
||||
if(const size_t leftover{(InSamples.size()-pos)&3})
|
||||
{
|
||||
const auto in = InSamples.last(leftover);
|
||||
const auto out = dst.subspan(pos);
|
||||
|
||||
std::transform(in.begin(), in.end(), out.begin(), out.begin(),
|
||||
[TargetGain](const float val, const float dry) noexcept -> float
|
||||
{ return dry + val*TargetGain; });
|
||||
}
|
||||
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)
|
||||
void Resample_<CubicTag,SSETag>(const InterpState *state, const al::span<const float> 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);
|
||||
const auto filter = std::get<CubicState>(*state).filter;
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
size_t pos{MaxResamplerEdge-1};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
|
||||
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));
|
||||
const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4,
|
||||
_mm_load_ps(filter[pi].mDeltas.data()));
|
||||
/* r = f*src */
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))};
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))};
|
||||
|
||||
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);
|
||||
const float output{_mm_cvtss_f32(r4)};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
void Resample_<BSincTag,SSETag>(const InterpState *state, const al::span<const float> 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};
|
||||
const auto &bsinc = std::get<BsincState>(*state);
|
||||
const auto sf4 = _mm_set1_ps(bsinc.sf);
|
||||
const auto m = size_t{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m);
|
||||
|
||||
ASSUME(bsinc.l <= MaxResamplerEdge);
|
||||
auto pos = size_t{MaxResamplerEdge-bsinc.l};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
auto 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};
|
||||
const auto pf4 = _mm_set1_ps(pf);
|
||||
const auto fil = filter.subspan(2_uz*pi*m);
|
||||
const auto phd = fil.subspan(m);
|
||||
const auto scd = fil.subspan(2_uz*BSincPhaseCount*m);
|
||||
const auto spd = scd.subspan(m);
|
||||
auto td = size_t{m >> 2};
|
||||
auto j = size_t{0};
|
||||
|
||||
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])));
|
||||
const __m128 f4 = vmadd(
|
||||
vmadd(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])),
|
||||
pf4, vmadd(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j])));
|
||||
/* r += f*src */
|
||||
r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
|
||||
r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+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);
|
||||
const auto output = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<FastBSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
void Resample_<FastBSincTag,SSETag>(const InterpState *state, const al::span<const float> 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};
|
||||
const auto &bsinc = std::get<BsincState>(*state);
|
||||
const auto m = size_t{bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(m <= MaxResamplerPadding);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
const auto filter = bsinc.filter.first(2_uz*m*BSincPhaseCount);
|
||||
|
||||
ASSUME(bsinc.l <= MaxResamplerEdge);
|
||||
size_t pos{MaxResamplerEdge-bsinc.l};
|
||||
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter,m]() -> float
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount);
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
auto 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};
|
||||
const auto pf4 = _mm_set1_ps(pf);
|
||||
const auto fil = filter.subspan(2_uz*m*pi);
|
||||
const auto phd = fil.subspan(m);
|
||||
auto td = size_t{m >> 2};
|
||||
auto j = size_t{0};
|
||||
|
||||
do {
|
||||
/* f = fil + pf*phd */
|
||||
const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j]));
|
||||
const auto f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j]));
|
||||
/* r += f*src */
|
||||
r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
|
||||
r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+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);
|
||||
const auto output = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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); }
|
||||
void MixHrtf_<SSETag>(const al::span<const float> InSamples, const al::span<float2> AccumSamples,
|
||||
const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo)
|
||||
{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); }
|
||||
|
||||
template<>
|
||||
void MixHrtfBlend_<SSETag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
|
||||
void MixHrtfBlend_<SSETag>(const al::span<const float> InSamples,
|
||||
const al::span<float2> AccumSamples, const uint IrSize, const HrtfFilter *oldparams,
|
||||
const MixHrtfFilter *newparams, const size_t SamplesToDo)
|
||||
{
|
||||
MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
|
||||
BufferSize);
|
||||
SamplesToDo);
|
||||
}
|
||||
|
||||
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)
|
||||
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
||||
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
|
||||
const size_t IrSize, const size_t SamplesToDo)
|
||||
{
|
||||
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
|
||||
IrSize, BufferSize);
|
||||
IrSize, SamplesToDo);
|
||||
}
|
||||
|
||||
|
||||
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 al::span<float> CurrentGains, const al::span<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;
|
||||
const auto fade_len = std::min(Counter, InSamples.size());
|
||||
const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len;
|
||||
|
||||
auto curgains = CurrentGains.begin();
|
||||
auto targetgains = TargetGains.cbegin();
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta,
|
||||
fade_len, realign_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<SSETag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<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;
|
||||
const auto fade_len = std::min(Counter, InSamples.size());
|
||||
const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,19 +23,42 @@
|
|||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "defs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
struct SSE2Tag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__)
|
||||
#pragma GCC target("sse2")
|
||||
#endif
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept
|
||||
{ return _mm_add_ps(x, _mm_mul_ps(y, z)); }
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
void Resample_<LerpTag,SSE2Tag>(const InterpState*, const al::span<const float> src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
|
@ -44,47 +67,148 @@ void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *RESTRICT src, u
|
|||
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_);
|
||||
std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{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)
|
||||
auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128
|
||||
{
|
||||
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])};
|
||||
const auto pos0 = static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
const auto pos1 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4)));
|
||||
const auto pos2 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8)));
|
||||
const auto pos3 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12)));
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])};
|
||||
const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])};
|
||||
|
||||
/* 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);
|
||||
return out;
|
||||
});
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
{
|
||||
auto pos = size_t{static_cast<uint>(_mm_cvtsi128_si32(pos4))};
|
||||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
const auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]()
|
||||
{
|
||||
const float smp{lerpf(src[pos+0], src[pos+1],
|
||||
static_cast<float>(frac) * (1.0f/MixerFracOne))};
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
return smp;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,SSE2Tag>(const InterpState *state, const al::span<const float> src,
|
||||
uint frac, const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const auto filter = std::get<CubicState>(*state).filter;
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)};
|
||||
const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)};
|
||||
|
||||
std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{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 vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]
|
||||
{
|
||||
const auto pos0 = static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
const auto pos1 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4)));
|
||||
const auto pos2 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8)));
|
||||
const auto pos3 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12)));
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
const __m128 val0{_mm_loadu_ps(&src[pos0])};
|
||||
const __m128 val1{_mm_loadu_ps(&src[pos1])};
|
||||
const __m128 val2{_mm_loadu_ps(&src[pos2])};
|
||||
const __m128 val3{_mm_loadu_ps(&src[pos3])};
|
||||
|
||||
const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)};
|
||||
const auto pi0 = static_cast<uint>(_mm_cvtsi128_si32(pi4));
|
||||
const auto pi1 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 4)));
|
||||
const auto pi2 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 8)));
|
||||
const auto pi3 = static_cast<uint>(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 12)));
|
||||
ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount);
|
||||
ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount);
|
||||
|
||||
const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)),
|
||||
fracDiffOne4)};
|
||||
|
||||
__m128 r0{_mm_mul_ps(val0,
|
||||
vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)),
|
||||
_mm_load_ps(filter[pi0].mDeltas.data())))};
|
||||
__m128 r1{_mm_mul_ps(val1,
|
||||
vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)),
|
||||
_mm_load_ps(filter[pi1].mDeltas.data())))};
|
||||
__m128 r2{_mm_mul_ps(val2,
|
||||
vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)),
|
||||
_mm_load_ps(filter[pi2].mDeltas.data())))};
|
||||
__m128 r3{_mm_mul_ps(val3,
|
||||
vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)),
|
||||
_mm_load_ps(filter[pi3].mDeltas.data())))};
|
||||
|
||||
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
|
||||
r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3));
|
||||
|
||||
frac4 = _mm_add_epi32(frac4, increment4);
|
||||
pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits));
|
||||
frac4 = _mm_and_si128(frac4, fracMask4);
|
||||
}
|
||||
return r0;
|
||||
});
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
if(const size_t todo{dst.size()&3})
|
||||
{
|
||||
src += static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
auto pos = size_t{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));
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter]
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
|
||||
const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4,
|
||||
_mm_load_ps(filter[pi].mDeltas.data()));
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))};
|
||||
|
||||
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));
|
||||
const float output{_mm_cvtss_f32(r4)};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,19 +24,42 @@
|
|||
#include <emmintrin.h>
|
||||
#include <smmintrin.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "defs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
struct SSE4Tag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__)
|
||||
#pragma GCC target("sse4.1")
|
||||
#endif
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept
|
||||
{ return _mm_add_ps(x, _mm_mul_ps(y, z)); }
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
void Resample_<LerpTag,SSE4Tag>(const InterpState*, const al::span<const float> src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
|
@ -45,35 +68,34 @@ void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *RESTRICT src, u
|
|||
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_);
|
||||
std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{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)
|
||||
auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]
|
||||
{
|
||||
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])};
|
||||
const auto pos0 = static_cast<uint>(_mm_extract_epi32(pos4, 0));
|
||||
const auto pos1 = static_cast<uint>(_mm_extract_epi32(pos4, 1));
|
||||
const auto pos2 = static_cast<uint>(_mm_extract_epi32(pos4, 2));
|
||||
const auto pos3 = static_cast<uint>(_mm_extract_epi32(pos4, 3));
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])};
|
||||
const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])};
|
||||
|
||||
/* 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);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
if(size_t todo{dst.size()&3})
|
||||
{
|
||||
|
|
@ -81,15 +103,117 @@ void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *RESTRICT src, u
|
|||
* four samples, so the lowest element is the next position to
|
||||
* resample.
|
||||
*/
|
||||
src += static_cast<uint>(_mm_cvtsi128_si32(pos4));
|
||||
auto pos = size_t{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));
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]
|
||||
{
|
||||
const float smp{lerpf(src[pos+0], src[pos+1],
|
||||
static_cast<float>(frac) * (1.0f/MixerFracOne))};
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
return smp;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<CubicTag,SSE4Tag>(const InterpState *state, const al::span<const float> src,
|
||||
uint frac, const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const auto filter = std::get<CubicState>(*state).filter;
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)};
|
||||
const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)};
|
||||
|
||||
std::array<uint,4> pos_{}, frac_{};
|
||||
InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{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 vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
|
||||
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]
|
||||
{
|
||||
const auto pos0 = static_cast<uint>(_mm_extract_epi32(pos4, 0));
|
||||
const auto pos1 = static_cast<uint>(_mm_extract_epi32(pos4, 1));
|
||||
const auto pos2 = static_cast<uint>(_mm_extract_epi32(pos4, 2));
|
||||
const auto pos3 = static_cast<uint>(_mm_extract_epi32(pos4, 3));
|
||||
ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3);
|
||||
const __m128 val0{_mm_loadu_ps(&src[pos0])};
|
||||
const __m128 val1{_mm_loadu_ps(&src[pos1])};
|
||||
const __m128 val2{_mm_loadu_ps(&src[pos2])};
|
||||
const __m128 val3{_mm_loadu_ps(&src[pos3])};
|
||||
|
||||
const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)};
|
||||
const auto pi0 = static_cast<uint>(_mm_extract_epi32(pi4, 0));
|
||||
const auto pi1 = static_cast<uint>(_mm_extract_epi32(pi4, 1));
|
||||
const auto pi2 = static_cast<uint>(_mm_extract_epi32(pi4, 2));
|
||||
const auto pi3 = static_cast<uint>(_mm_extract_epi32(pi4, 3));
|
||||
ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount);
|
||||
ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount);
|
||||
|
||||
const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)),
|
||||
fracDiffOne4)};
|
||||
|
||||
__m128 r0{_mm_mul_ps(val0,
|
||||
vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)),
|
||||
_mm_load_ps(filter[pi0].mDeltas.data())))};
|
||||
__m128 r1{_mm_mul_ps(val1,
|
||||
vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)),
|
||||
_mm_load_ps(filter[pi1].mDeltas.data())))};
|
||||
__m128 r2{_mm_mul_ps(val2,
|
||||
vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)),
|
||||
_mm_load_ps(filter[pi2].mDeltas.data())))};
|
||||
__m128 r3{_mm_mul_ps(val3,
|
||||
vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()),
|
||||
_mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)),
|
||||
_mm_load_ps(filter[pi3].mDeltas.data())))};
|
||||
|
||||
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
|
||||
r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3));
|
||||
|
||||
frac4 = _mm_add_epi32(frac4, increment4);
|
||||
pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits));
|
||||
frac4 = _mm_and_si128(frac4, fracMask4);
|
||||
return r0;
|
||||
});
|
||||
|
||||
if(const size_t todo{dst.size()&3})
|
||||
{
|
||||
auto pos = size_t{static_cast<uint>(_mm_cvtsi128_si32(pos4))};
|
||||
frac = static_cast<uint>(_mm_cvtsi128_si32(frac4));
|
||||
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter]
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
|
||||
const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4,
|
||||
_mm_load_ps(filter[pi].mDeltas.data()));
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))};
|
||||
|
||||
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));
|
||||
const float output{_mm_cvtss_f32(r4)};
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
constexpr unsigned int MaxResamplerPadding{48};
|
||||
|
||||
constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1};
|
||||
constexpr unsigned int MaxResamplerEdge{MaxResamplerPadding >> 1};
|
||||
|
||||
#endif /* CORE_RESAMPLER_LIMITS_H */
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@
|
|||
|
||||
#include "rtkit.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <cerrno>
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef __linux__
|
||||
|
|
@ -153,29 +153,29 @@ int rtkit_get_int_property(DBusConnection *connection, const char *propname, lon
|
|||
|
||||
} // namespace
|
||||
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *connection)
|
||||
int rtkit_get_max_realtime_priority(DBusConnection *system_bus)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)};
|
||||
int err{rtkit_get_int_property(system_bus, "MaxRealtimePriority", &retval)};
|
||||
return err < 0 ? err : static_cast<int>(retval);
|
||||
}
|
||||
|
||||
int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level)
|
||||
int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)};
|
||||
int err{rtkit_get_int_property(system_bus, "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 rtkit_get_rttime_usec_max(DBusConnection *system_bus)
|
||||
{
|
||||
long long retval{};
|
||||
int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)};
|
||||
int err{rtkit_get_int_property(system_bus, "RTTimeUSecMax", &retval)};
|
||||
return err < 0 ? err : retval;
|
||||
}
|
||||
|
||||
int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority)
|
||||
int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
|
|
@ -195,7 +195,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority)
|
|||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level)
|
||||
int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level)
|
||||
{
|
||||
if(thread == 0)
|
||||
thread = _gettid();
|
||||
|
|
@ -225,7 +225,7 @@ int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_
|
|||
if(!ready) return -ENOMEM;
|
||||
|
||||
dbus::Error error;
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1,
|
||||
dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1,
|
||||
&error.get())};
|
||||
if(!r) return translate_error(error->name);
|
||||
|
||||
|
|
|
|||
85
Engine/lib/openal-soft/core/storage_formats.cpp
Normal file
85
Engine/lib/openal-soft/core/storage_formats.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "storage_formats.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
const char *NameFromFormat(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return "UInt8";
|
||||
case FmtShort: return "Int16";
|
||||
case FmtInt: return "Int32";
|
||||
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";
|
||||
case FmtMonoDup: return "Mono (dup)";
|
||||
}
|
||||
return "<internal error>";
|
||||
}
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return sizeof(std::uint8_t);
|
||||
case FmtShort: return sizeof(std::int16_t);
|
||||
case FmtInt: return sizeof(std::int32_t);
|
||||
case FmtFloat: return sizeof(float);
|
||||
case FmtDouble: return sizeof(double);
|
||||
case FmtMulaw: return sizeof(std::uint8_t);
|
||||
case FmtAlaw: return sizeof(std::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;
|
||||
case FmtMonoDup: return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
54
Engine/lib/openal-soft/core/storage_formats.h
Normal file
54
Engine/lib/openal-soft/core/storage_formats.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef CORE_STORAGE_FORMATS_H
|
||||
#define CORE_STORAGE_FORMATS_H
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType : unsigned char {
|
||||
FmtUByte,
|
||||
FmtShort,
|
||||
FmtInt,
|
||||
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. */
|
||||
FmtMonoDup, /* Mono duplicated for left/right separation */
|
||||
};
|
||||
|
||||
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); }
|
||||
|
||||
#endif /* CORE_STORAGE_FORMATS_H */
|
||||
|
|
@ -4,54 +4,150 @@
|
|||
#include "uhjfilter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alnumeric.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "opthelpers.h"
|
||||
#include "pffft.h"
|
||||
#include "phase_shifter.h"
|
||||
|
||||
|
||||
UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
|
||||
UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const PhaseShifterT<UhjLength256> PShiftLq{};
|
||||
const PhaseShifterT<UhjLength512> PShiftHq{};
|
||||
template<std::size_t A, typename T, std::size_t N>
|
||||
constexpr auto assume_aligned_span(const al::span<T,N> s) noexcept -> al::span<T,N>
|
||||
{ return al::span<T,N>{al::assume_aligned<A>(s.data()), s.size()}; }
|
||||
|
||||
/* Convolution is implemented using a segmented overlap-add method. The filter
|
||||
* response is broken up into multiple segments of 128 samples, and each
|
||||
* segment has an FFT applied with a 256-sample buffer (the latter half left
|
||||
* silent) to get its frequency-domain response.
|
||||
*
|
||||
* Input samples are similarly broken up into 128-sample segments, with a 256-
|
||||
* sample FFT applied to each new incoming segment to get its frequency-domain
|
||||
* response. A history of FFT'd input segments is maintained, equal to the
|
||||
* number of filter response segments.
|
||||
*
|
||||
* To apply the convolution, each filter response segment is convolved with its
|
||||
* paired input segment (using complex multiplies, far cheaper than time-domain
|
||||
* FIRs), accumulating into an FFT buffer. The input history is then shifted to
|
||||
* align with later filter response segments for the next input segment.
|
||||
*
|
||||
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
|
||||
* sample time-domain response for output, which is split in two halves. The
|
||||
* first half is the 128-sample output, and the second half is a 128-sample
|
||||
* (really, 127) delayed extension, which gets added to the output next time.
|
||||
* Convolving two time-domain responses of length N results in a time-domain
|
||||
* signal of length N*2 - 1, and this holds true regardless of the convolution
|
||||
* being applied in the frequency domain, so these "overflow" samples need to
|
||||
* be accounted for.
|
||||
*/
|
||||
template<size_t N>
|
||||
struct SegmentedFilter {
|
||||
static constexpr size_t sFftLength{256};
|
||||
static constexpr size_t sSampleLength{sFftLength / 2};
|
||||
static constexpr size_t sNumSegments{N/sSampleLength};
|
||||
static_assert(N >= sFftLength);
|
||||
static_assert((N % sSampleLength) == 0);
|
||||
|
||||
PFFFTSetup mFft;
|
||||
alignas(16) std::array<float,sFftLength*sNumSegments> mFilterData;
|
||||
|
||||
SegmentedFilter() : mFft{sFftLength, PFFFT_REAL}
|
||||
{
|
||||
static constexpr size_t fft_size{N};
|
||||
|
||||
/* To set up the filter, we first need to generate the desired
|
||||
* response (not reversed).
|
||||
*/
|
||||
auto tmpBuffer = std::vector<double>(fft_size, 0.0);
|
||||
for(std::size_t i{0};i < fft_size/2;++i)
|
||||
{
|
||||
const int k{int{fft_size/2} - static_cast<int>(i*2 + 1)};
|
||||
|
||||
const double w{2.0*al::numbers::pi * static_cast<double>(i*2 + 1)
|
||||
/ double{fft_size}};
|
||||
const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w)
|
||||
- 0.0106411*std::cos(3.0*w)};
|
||||
|
||||
const double pk{al::numbers::pi * static_cast<double>(k)};
|
||||
tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk;
|
||||
}
|
||||
|
||||
/* The segments of the filter are converted back to the frequency
|
||||
* domain, each on their own (0 stuffed).
|
||||
*/
|
||||
using complex_d = std::complex<double>;
|
||||
auto fftBuffer = std::vector<complex_d>(sFftLength);
|
||||
auto fftTmp = al::vector<float,16>(sFftLength);
|
||||
auto filter = mFilterData.begin();
|
||||
for(size_t s{0};s < sNumSegments;++s)
|
||||
{
|
||||
const auto tmpspan = al::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength);
|
||||
auto iter = std::copy_n(tmpspan.cbegin(), tmpspan.size(), fftBuffer.begin());
|
||||
std::fill(iter, fftBuffer.end(), complex_d{});
|
||||
forward_fft(fftBuffer);
|
||||
|
||||
/* Convert to zdomain data for PFFFT, scaled by the FFT length so
|
||||
* the iFFT result will be normalized.
|
||||
*/
|
||||
for(size_t i{0};i < sSampleLength;++i)
|
||||
{
|
||||
fftTmp[i*2 + 0] = static_cast<float>(fftBuffer[i].real()) / float{sFftLength};
|
||||
fftTmp[i*2 + 1] = static_cast<float>((i == 0) ? fftBuffer[sSampleLength].real()
|
||||
: fftBuffer[i].imag()) / float{sFftLength};
|
||||
}
|
||||
mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD);
|
||||
filter += sFftLength;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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; } };
|
||||
const SegmentedFilter<N> gSegmentedFilter;
|
||||
|
||||
template<size_t N>
|
||||
const PhaseShifterT<N> PShifter;
|
||||
|
||||
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)
|
||||
0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f
|
||||
}};
|
||||
/* 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)
|
||||
0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
void UhjAllPassFilter::processOne(const al::span<const float, 4> coeffs, float x)
|
||||
{
|
||||
auto state = mState;
|
||||
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;
|
||||
}
|
||||
mState = state;
|
||||
}
|
||||
|
||||
void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
|
||||
const al::span<const float> src, const bool updateState, float *RESTRICT dst)
|
||||
const al::span<const float> src, const bool updateState, const al::span<float> dst)
|
||||
{
|
||||
auto state = mState;
|
||||
|
||||
|
|
@ -66,7 +162,7 @@ void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
|
|||
}
|
||||
return x;
|
||||
};
|
||||
std::transform(src.begin(), src.end(), dst, proc_sample);
|
||||
std::transform(src.begin(), src.end(), dst.begin(), proc_sample);
|
||||
if(updateState) LIKELY mState = state;
|
||||
}
|
||||
|
||||
|
|
@ -92,68 +188,138 @@ 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();
|
||||
static constexpr auto &Filter = gSegmentedFilter<N>;
|
||||
static_assert(sFftLength == Filter.sFftLength);
|
||||
static_assert(sSegmentSize == Filter.sSampleLength);
|
||||
static_assert(sNumSegments == Filter.sNumSegments);
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
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])};
|
||||
const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo};
|
||||
const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo};
|
||||
const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo};
|
||||
|
||||
std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
|
||||
std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
|
||||
std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
|
||||
std::copy_n(winput.begin(), SamplesToDo, mW.begin()+sFilterDelay);
|
||||
std::copy_n(xinput.begin(), SamplesToDo, mX.begin()+sFilterDelay);
|
||||
std::copy_n(yinput.begin(), 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];
|
||||
std::transform(mW.begin(), mW.begin()+SamplesToDo, mX.begin(), mS.begin(),
|
||||
[](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; });
|
||||
|
||||
/* 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());
|
||||
auto dstore = mD.begin();
|
||||
size_t curseg{mCurrentSegment};
|
||||
for(size_t base{0};base < SamplesToDo;)
|
||||
{
|
||||
const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)};
|
||||
auto wseg = winput.subspan(base, todo);
|
||||
auto xseg = xinput.subspan(base, todo);
|
||||
auto wxio = al::span{mWXInOut}.subspan(mFifoPos, todo);
|
||||
|
||||
/* Copy out the samples that were previously processed by the FFT. */
|
||||
dstore = std::copy_n(wxio.begin(), todo, dstore);
|
||||
|
||||
/* Transform the non-delayed input and store in the front half of the
|
||||
* filter input.
|
||||
*/
|
||||
std::transform(wseg.begin(), wseg.end(), xseg.begin(), wxio.begin(),
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return -0.3420201f*w + 0.5098604f*x; });
|
||||
|
||||
mFifoPos += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether the input buffer is filled with new samples. */
|
||||
if(mFifoPos < sSegmentSize) break;
|
||||
mFifoPos = 0;
|
||||
|
||||
/* Copy the new input to the next history segment, clearing the back
|
||||
* half of the segment, and convert to the frequency domain.
|
||||
*/
|
||||
auto input = mWXHistory.begin() + curseg*sFftLength;
|
||||
std::copy_n(mWXInOut.begin(), sSegmentSize, input);
|
||||
std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f);
|
||||
|
||||
Filter.mFft.transform(al::to_address(input), al::to_address(input), mWorkData.data(),
|
||||
PFFFT_FORWARD);
|
||||
|
||||
/* Convolve each input segment with its IR filter counterpart (aligned
|
||||
* in time, from newest to oldest).
|
||||
*/
|
||||
mFftBuffer.fill(0.0f);
|
||||
auto filter = Filter.mFilterData.begin();
|
||||
for(size_t s{curseg};s < sNumSegments;++s)
|
||||
{
|
||||
Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
|
||||
mFftBuffer.data());
|
||||
input += sFftLength;
|
||||
filter += sFftLength;
|
||||
}
|
||||
input = mWXHistory.begin();
|
||||
for(size_t s{0};s < curseg;++s)
|
||||
{
|
||||
Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
|
||||
mFftBuffer.data());
|
||||
input += sFftLength;
|
||||
filter += sFftLength;
|
||||
}
|
||||
|
||||
/* Convert back to samples, writing to the output and storing the extra
|
||||
* for next time.
|
||||
*/
|
||||
Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(),
|
||||
PFFFT_BACKWARD);
|
||||
|
||||
std::transform(mFftBuffer.begin(), mFftBuffer.begin()+sSegmentSize,
|
||||
mWXInOut.begin()+sSegmentSize, mWXInOut.begin(), std::plus{});
|
||||
std::copy_n(mFftBuffer.begin()+sSegmentSize, sSegmentSize, mWXInOut.begin()+sSegmentSize);
|
||||
|
||||
/* Shift the input history. */
|
||||
curseg = curseg ? (curseg-1) : (sNumSegments-1);
|
||||
}
|
||||
mCurrentSegment = curseg;
|
||||
|
||||
/* 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];
|
||||
std::transform(mD.begin(), mD.begin()+SamplesToDo, mY.begin(), mD.begin(),
|
||||
[](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; });
|
||||
|
||||
/* 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();
|
||||
auto delayBuffer = mDirectDelay.begin();
|
||||
for(float *buffer : {LeftOut, RightOut})
|
||||
{
|
||||
float *distbuf{al::assume_aligned<16>(delayBuffer->data())};
|
||||
const auto distbuf = assume_aligned_span<16>(al::span{*delayBuffer});
|
||||
++delayBuffer;
|
||||
|
||||
float *inout{al::assume_aligned<16>(buffer)};
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if(SamplesToDo >= sFilterDelay) LIKELY
|
||||
const auto inout = al::span{al::assume_aligned<16>(buffer), SamplesToDo};
|
||||
if(SamplesToDo >= sFilterDelay)
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end);
|
||||
std::swap_ranges(inout, delay_end, distbuf);
|
||||
auto delay_end = std::rotate(inout.begin(), inout.end() - sFilterDelay, inout.end());
|
||||
std::swap_ranges(inout.begin(), delay_end, distbuf.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
|
||||
std::rotate(distbuf, delay_start, distbuf + sFilterDelay);
|
||||
auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin());
|
||||
std::rotate(distbuf.begin(), delay_start, distbuf.begin() + 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++)
|
||||
const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo};
|
||||
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++)
|
||||
const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo};
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
right[i] += (mS[i] - mD[i]) * 0.5f;
|
||||
}
|
||||
|
||||
|
|
@ -176,47 +342,51 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut,
|
|||
const al::span<const float *const, 3> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(SamplesToDo <= BufferLineSize);
|
||||
|
||||
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])};
|
||||
const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo};
|
||||
const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo};
|
||||
const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo};
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
|
||||
std::transform(winput.begin(), winput.end(), xinput.begin(), 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);
|
||||
mFilter1WX.process(Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true,
|
||||
al::span{mS}.subspan(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(),
|
||||
std::transform(winput.begin(), winput.end(), xinput.begin(), 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());
|
||||
mFilter2WX.process(Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX);
|
||||
|
||||
/* Apply filter1 to Y and store in mD. */
|
||||
mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1);
|
||||
mFilter1Y.process(Filter1Coeff, yinput, true, al::span{mD}.subspan(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];
|
||||
std::transform(mWX.begin(), mWX.begin()+SamplesToDo, mD.begin(), mD.begin(),
|
||||
[](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; });
|
||||
|
||||
/* Apply the base filter to the existing output to align with the processed
|
||||
* signal.
|
||||
*/
|
||||
mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1);
|
||||
mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true,
|
||||
al::span{mTemp}.subspan(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++)
|
||||
const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo};
|
||||
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);
|
||||
mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true,
|
||||
al::span{mTemp}.subspan(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++)
|
||||
const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo};
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
right[i] = (mS[i] - mD[i])*0.5f + mTemp[i];
|
||||
}
|
||||
|
||||
|
|
@ -240,31 +410,29 @@ void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesT
|
|||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
constexpr auto &PShift = PShifter<N>;
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
ASSUME(samplesToDo <= BufferLineSize);
|
||||
|
||||
{
|
||||
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])};
|
||||
const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding};
|
||||
const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding};
|
||||
const auto t = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{});
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{});
|
||||
|
||||
/* T */
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mT[i] = t[i];
|
||||
std::copy(t.begin(), t.end(), mT.begin());
|
||||
}
|
||||
|
||||
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])};
|
||||
const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo};
|
||||
const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo};
|
||||
const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo};
|
||||
|
||||
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
|
|
@ -272,21 +440,22 @@ void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesT
|
|||
[](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());
|
||||
PShift.process(xoutput, mTemp);
|
||||
|
||||
/* 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];
|
||||
std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(),
|
||||
[](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; });
|
||||
|
||||
/* 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];
|
||||
std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(),
|
||||
[](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; });
|
||||
|
||||
/* 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());
|
||||
PShift.process(youtput, mTemp);
|
||||
|
||||
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
|
|
@ -294,10 +463,10 @@ void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesT
|
|||
|
||||
if(samples.size() > 3)
|
||||
{
|
||||
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
|
||||
const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo};
|
||||
/* Z = 1.023332*Q */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
zoutput[i] = 1.023332f*zoutput[i];
|
||||
std::transform(zoutput.begin(), zoutput.end(), zoutput.begin(),
|
||||
[](const float q) noexcept { return 1.023332f*q; });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -307,70 +476,67 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
|
|||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
ASSUME(samplesToDo <= BufferLineSize);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding};
|
||||
const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{});
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{});
|
||||
}
|
||||
|
||||
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])};
|
||||
const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo};
|
||||
const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo};
|
||||
const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding};
|
||||
|
||||
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(),
|
||||
std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(),
|
||||
mTemp.begin(),
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput);
|
||||
if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]);
|
||||
mFilter2DT.process(Filter2Coeff, al::span{mTemp}.subspan(1,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];
|
||||
mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp);
|
||||
|
||||
/* 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];
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(),
|
||||
[](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; });
|
||||
/* 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];
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(),
|
||||
[](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; });
|
||||
|
||||
|
||||
/* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput,
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(),
|
||||
[](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];
|
||||
mFilter1DT.process(Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp);
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
|
||||
mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, 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];
|
||||
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(),
|
||||
[](const float dt, const float js) noexcept { return dt + 0.186633f*js; });
|
||||
|
||||
if(samples.size() > 3)
|
||||
{
|
||||
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
|
||||
const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo};
|
||||
|
||||
/* 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];
|
||||
mFilter1Q.process(Filter1Coeff, zoutput, updateState, mTemp);
|
||||
|
||||
/* Z = 1.023332*Q */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
zoutput[i] = 1.023332f*mTemp[i];
|
||||
std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(),
|
||||
[](const float q) noexcept { return 1.023332f*q; });
|
||||
}
|
||||
|
||||
mFirstRun = false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -392,16 +558,16 @@ void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t sa
|
|||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
constexpr auto &PShift = PShifter<N>;
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
ASSUME(samplesToDo <= BufferLineSize);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding};
|
||||
const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding};
|
||||
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{});
|
||||
|
||||
/* Pre-apply the width factor to the difference signal D. Smoothly
|
||||
* interpolate when it changes.
|
||||
|
|
@ -410,53 +576,60 @@ void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t sa
|
|||
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;
|
||||
std::transform(left.begin(), left.end(), right.begin(), mD.begin(),
|
||||
[wcurrent](const float l, const float r) noexcept { return (l-r) * 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;
|
||||
|
||||
const auto lfade = left.first(samplesToDo);
|
||||
auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(),
|
||||
[wcurrent,wstep,&fi](const float l, const float r) noexcept
|
||||
{
|
||||
const float ret{(l-r) * (wcurrent + wstep*fi)};
|
||||
fi += 1.0f;
|
||||
return ret;
|
||||
});
|
||||
|
||||
const auto lend = left.subspan(samplesToDo);
|
||||
const auto rend = right.subspan(samplesToDo);
|
||||
std::transform(lend.begin(), lend.end(), rend.begin(), dstore,
|
||||
[wtarget](const float l, const float r) noexcept { return (l-r) * 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])};
|
||||
const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo};
|
||||
const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo};
|
||||
const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo};
|
||||
|
||||
/* 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());
|
||||
PShift.process(xoutput, mTemp);
|
||||
|
||||
/* 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];
|
||||
std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(),
|
||||
[](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; });
|
||||
/* 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];
|
||||
std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(),
|
||||
[](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; });
|
||||
|
||||
/* 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());
|
||||
PShift.process(youtput, mTemp);
|
||||
|
||||
/* 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];
|
||||
std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(),
|
||||
[](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; });
|
||||
}
|
||||
|
||||
void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
|
|
@ -465,13 +638,13 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
|
|||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
ASSUME(samplesToDo <= BufferLineSize);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding};
|
||||
const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding};
|
||||
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{});
|
||||
|
||||
/* Pre-apply the width factor to the difference signal D. Smoothly
|
||||
* interpolate when it changes.
|
||||
|
|
@ -480,53 +653,63 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
|
|||
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;
|
||||
std::transform(left.begin(), left.end(), right.begin(), mD.begin(),
|
||||
[wcurrent](const float l, const float r) noexcept
|
||||
{ return (l-r) * 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;
|
||||
}
|
||||
|
||||
const auto lfade = left.first(samplesToDo);
|
||||
auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(),
|
||||
[wcurrent,wstep,&fi](const float l, const float r) noexcept
|
||||
{
|
||||
const float ret{(l-r) * (wcurrent + wstep*fi)};
|
||||
fi += 1.0f;
|
||||
return ret;
|
||||
});
|
||||
|
||||
const auto lend = left.subspan(samplesToDo);
|
||||
const auto rend = right.subspan(samplesToDo);
|
||||
std::transform(lend.begin(), lend.end(), rend.begin(), dstore,
|
||||
[wtarget](const float l, const float r) noexcept { return (l-r) * 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])};
|
||||
const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo};
|
||||
const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo};
|
||||
const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo};
|
||||
|
||||
/* 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];
|
||||
mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp);
|
||||
|
||||
/* Precompute j*D and store in xoutput. */
|
||||
mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput);
|
||||
if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]);
|
||||
mFilter2D.process(Filter2Coeff, al::span{mD}.subspan(1, 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];
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(),
|
||||
[](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; });
|
||||
/* 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];
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(),
|
||||
[](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; });
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
|
||||
mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, 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];
|
||||
mFilter1D.process(Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp);
|
||||
|
||||
/* 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];
|
||||
std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(),
|
||||
[](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; });
|
||||
|
||||
mFirstRun = false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,42 +2,50 @@
|
|||
#define CORE_UHJFILTER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
|
||||
|
||||
static constexpr size_t UhjLength256{256};
|
||||
static constexpr size_t UhjLength512{512};
|
||||
inline constexpr std::size_t UhjLength256{256};
|
||||
inline constexpr std::size_t UhjLength512{512};
|
||||
|
||||
enum class UhjQualityType : uint8_t {
|
||||
enum class UhjQualityType : std::uint8_t {
|
||||
IIR = 0,
|
||||
FIR256,
|
||||
FIR512,
|
||||
Default = IIR
|
||||
};
|
||||
|
||||
extern UhjQualityType UhjDecodeQuality;
|
||||
extern UhjQualityType UhjEncodeQuality;
|
||||
inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
|
||||
inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
|
||||
|
||||
|
||||
struct UhjAllPassFilter {
|
||||
struct AllPassState {
|
||||
/* Last two delayed components for direct form II. */
|
||||
float z[2];
|
||||
std::array<float,2> z{};
|
||||
};
|
||||
std::array<AllPassState,4> mState;
|
||||
|
||||
void processOne(const al::span<const float,4> coeffs, float x);
|
||||
void process(const al::span<const float,4> coeffs, const al::span<const float> src,
|
||||
const bool update, float *RESTRICT dst);
|
||||
const bool update, const al::span<float> dst);
|
||||
};
|
||||
|
||||
|
||||
struct UhjEncoderBase {
|
||||
UhjEncoderBase() = default;
|
||||
UhjEncoderBase(const UhjEncoderBase&) = delete;
|
||||
UhjEncoderBase(UhjEncoderBase&&) = delete;
|
||||
virtual ~UhjEncoderBase() = default;
|
||||
|
||||
virtual size_t getDelay() noexcept = 0;
|
||||
void operator=(const UhjEncoderBase&) = delete;
|
||||
void operator=(UhjEncoderBase&&) = delete;
|
||||
|
||||
virtual std::size_t getDelay() noexcept = 0;
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
|
|
@ -45,12 +53,15 @@ struct UhjEncoderBase {
|
|||
* 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;
|
||||
const al::span<const float*const,3> InSamples, const std::size_t SamplesToDo) = 0;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
template<std::size_t N>
|
||||
struct UhjEncoder final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{N/2};
|
||||
static constexpr std::size_t sFftLength{256};
|
||||
static constexpr std::size_t sSegmentSize{sFftLength/2};
|
||||
static constexpr std::size_t sNumSegments{N/sSegmentSize};
|
||||
static constexpr std::size_t sFilterDelay{N/2 + sSegmentSize};
|
||||
|
||||
/* Delays and processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
|
||||
|
|
@ -60,15 +71,16 @@ struct UhjEncoder final : public UhjEncoderBase {
|
|||
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{};
|
||||
/* History and temp storage for the convolution filter. */
|
||||
std::size_t mFifoPos{}, mCurrentSegment{};
|
||||
alignas(16) std::array<float,sFftLength> mWXInOut{};
|
||||
alignas(16) std::array<float,sFftLength> mFftBuffer{};
|
||||
alignas(16) std::array<float,sFftLength> mWorkData{};
|
||||
alignas(16) std::array<float,sFftLength*sNumSegments> mWXHistory{};
|
||||
|
||||
alignas(16) std::array<std::array<float,sFilterDelay>,2> mDirectDelay{};
|
||||
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
std::size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
|
|
@ -76,13 +88,11 @@ struct UhjEncoder final : public UhjEncoderBase {
|
|||
* 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)
|
||||
const std::size_t SamplesToDo) final;
|
||||
};
|
||||
|
||||
struct UhjEncoderIIR final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{1};
|
||||
static constexpr std::size_t sFilterDelay{1};
|
||||
|
||||
/* Processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+1> mS{};
|
||||
|
|
@ -98,7 +108,7 @@ struct UhjEncoderIIR final : public UhjEncoderBase {
|
|||
std::array<UhjAllPassFilter,2> mFilter1Direct;
|
||||
std::array<float,2> mDirectDelay{};
|
||||
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
std::size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
|
|
@ -106,22 +116,26 @@ struct UhjEncoderIIR final : public UhjEncoderBase {
|
|||
* 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)
|
||||
const std::size_t SamplesToDo) final;
|
||||
};
|
||||
|
||||
|
||||
struct DecoderBase {
|
||||
static constexpr size_t sMaxPadding{256};
|
||||
static constexpr std::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};
|
||||
|
||||
DecoderBase() = default;
|
||||
DecoderBase(const DecoderBase&) = delete;
|
||||
DecoderBase(DecoderBase&&) = delete;
|
||||
virtual ~DecoderBase() = default;
|
||||
|
||||
virtual void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
void operator=(const DecoderBase&) = delete;
|
||||
void operator=(DecoderBase&&) = delete;
|
||||
|
||||
virtual void decode(const al::span<float*> samples, const std::size_t samplesToDo,
|
||||
const bool updateState) = 0;
|
||||
|
||||
/**
|
||||
|
|
@ -131,10 +145,10 @@ struct DecoderBase {
|
|||
float mWidthControl{0.593f};
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
template<std::size_t N>
|
||||
struct UhjDecoder final : public DecoderBase {
|
||||
/* The number of extra sample frames needed for input. */
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
static constexpr std::size_t sInputPadding{N/2};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
|
|
@ -153,24 +167,23 @@ struct UhjDecoder final : public DecoderBase {
|
|||
* 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)
|
||||
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
|
||||
const bool updateState) final;
|
||||
};
|
||||
|
||||
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.
|
||||
/* These IIR decoder filters normally have a 1-sample delay on the non-
|
||||
* filtered components. However, the filtered components are made to skip
|
||||
* the first output sample and take one future sample, which puts it ahead
|
||||
* by one sample. The first filtered output sample is cut to align it with
|
||||
* the first non-filtered sample, similar to the FIR filters.
|
||||
*/
|
||||
static constexpr size_t sInputPadding{0};
|
||||
static constexpr std::size_t sInputPadding{1};
|
||||
|
||||
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{};
|
||||
bool mFirstRun{true};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mTemp{};
|
||||
|
||||
UhjAllPassFilter mFilter1S;
|
||||
UhjAllPassFilter mFilter2DT;
|
||||
|
|
@ -178,15 +191,13 @@ struct UhjDecoderIIR final : public DecoderBase {
|
|||
UhjAllPassFilter mFilter2S;
|
||||
UhjAllPassFilter mFilter1Q;
|
||||
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjDecoderIIR)
|
||||
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
|
||||
const bool updateState) final;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
template<std::size_t N>
|
||||
struct UhjStereoDecoder final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
static constexpr std::size_t sInputPadding{N/2};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
|
|
@ -204,31 +215,27 @@ struct UhjStereoDecoder final : public DecoderBase {
|
|||
* 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)
|
||||
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
|
||||
const bool updateState) final;
|
||||
};
|
||||
|
||||
struct UhjStereoDecoderIIR final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{0};
|
||||
static constexpr std::size_t sInputPadding{1};
|
||||
|
||||
bool mFirstRun{true};
|
||||
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{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize> mTemp{};
|
||||
|
||||
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)
|
||||
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
|
||||
const bool updateState) final;
|
||||
};
|
||||
|
||||
#endif /* CORE_UHJFILTER_H */
|
||||
|
|
|
|||
|
|
@ -4,14 +4,10 @@
|
|||
|
||||
#ifndef AL_NO_UID_DEFS
|
||||
|
||||
#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
|
||||
#if defined(HAVE_GUIDDEF_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);
|
||||
|
|
@ -19,12 +15,8 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x
|
|||
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
|
||||
#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP)
|
||||
#include <wtypes.h>
|
||||
#include <devpropdef.h>
|
||||
#include <propkeydef.h>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,13 +5,12 @@
|
|||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "aloptional.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
#include "buffer_storage.h"
|
||||
|
|
@ -33,7 +32,7 @@ enum class DistanceModel : unsigned char;
|
|||
using uint = unsigned int;
|
||||
|
||||
|
||||
#define MAX_SENDS 6
|
||||
inline constexpr size_t MaxSendCount{6};
|
||||
|
||||
|
||||
enum class SpatializeMode : unsigned char {
|
||||
|
|
@ -49,7 +48,7 @@ enum class DirectMode : unsigned char {
|
|||
};
|
||||
|
||||
|
||||
constexpr uint MaxPitch{10};
|
||||
inline constexpr uint MaxPitch{10};
|
||||
|
||||
|
||||
enum {
|
||||
|
|
@ -66,26 +65,29 @@ struct DirectParams {
|
|||
|
||||
NfcFilter NFCtrlFilter;
|
||||
|
||||
struct {
|
||||
HrtfFilter Old;
|
||||
HrtfFilter Target;
|
||||
alignas(16) std::array<float,HrtfHistoryLength> History;
|
||||
} Hrtf;
|
||||
struct HrtfParams {
|
||||
HrtfFilter Old{};
|
||||
HrtfFilter Target{};
|
||||
alignas(16) std::array<float,HrtfHistoryLength> History{};
|
||||
};
|
||||
HrtfParams Hrtf;
|
||||
|
||||
struct {
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Current;
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Target;
|
||||
} Gains;
|
||||
struct GainParams {
|
||||
std::array<float,MaxOutputChannels> Current{};
|
||||
std::array<float,MaxOutputChannels> Target{};
|
||||
};
|
||||
GainParams Gains;
|
||||
};
|
||||
|
||||
struct SendParams {
|
||||
BiquadFilter LowPass;
|
||||
BiquadFilter HighPass;
|
||||
|
||||
struct {
|
||||
std::array<float,MaxAmbiChannels> Current;
|
||||
std::array<float,MaxAmbiChannels> Target;
|
||||
} Gains;
|
||||
struct GainParams {
|
||||
std::array<float,MaxAmbiChannels> Current{};
|
||||
std::array<float,MaxAmbiChannels> Target{};
|
||||
};
|
||||
GainParams Gains;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -100,7 +102,7 @@ struct VoiceBufferItem {
|
|||
uint mLoopStart{0u};
|
||||
uint mLoopEnd{0u};
|
||||
|
||||
al::byte *mSamples{nullptr};
|
||||
al::span<std::byte> mSamples{};
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -139,15 +141,18 @@ struct VoiceProps {
|
|||
|
||||
float Radius;
|
||||
float EnhWidth;
|
||||
float Panning;
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
struct DirectData {
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
} Direct;
|
||||
};
|
||||
DirectData Direct;
|
||||
|
||||
struct SendData {
|
||||
EffectSlot *Slot;
|
||||
float Gain;
|
||||
|
|
@ -155,13 +160,12 @@ struct VoiceProps {
|
|||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
} Send[MAX_SENDS];
|
||||
};
|
||||
std::array<SendData,MaxSendCount> Send;
|
||||
};
|
||||
|
||||
struct VoicePropsItem : public VoiceProps {
|
||||
std::atomic<VoicePropsItem*> next{nullptr};
|
||||
|
||||
DEF_NEWDEL(VoicePropsItem)
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
|
|
@ -186,7 +190,7 @@ struct Voice {
|
|||
|
||||
std::atomic<VoicePropsItem*> mUpdate{nullptr};
|
||||
|
||||
VoiceProps mProps;
|
||||
VoiceProps mProps{};
|
||||
|
||||
std::atomic<uint> mSourceID{0u};
|
||||
std::atomic<State> mPlayState{Stopped};
|
||||
|
|
@ -196,30 +200,30 @@ struct Voice {
|
|||
* Source offset in samples, relative to the currently playing buffer, NOT
|
||||
* the whole queue.
|
||||
*/
|
||||
std::atomic<int> mPosition;
|
||||
std::atomic<int> mPosition{};
|
||||
/** Fractional (fixed-point) offset to the next sample. */
|
||||
std::atomic<uint> mPositionFrac;
|
||||
std::atomic<uint> mPositionFrac{};
|
||||
|
||||
/* Current buffer queue item being played. */
|
||||
std::atomic<VoiceBufferItem*> mCurrentBuffer;
|
||||
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::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;
|
||||
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{};
|
||||
|
|
@ -227,20 +231,20 @@ struct Voice {
|
|||
/** Current target parameters used for mixing. */
|
||||
uint mStep{0};
|
||||
|
||||
ResamplerFunc mResampler;
|
||||
ResamplerFunc mResampler{};
|
||||
|
||||
InterpState mResampleState;
|
||||
InterpState mResampleState{};
|
||||
|
||||
std::bitset<VoiceFlagCount> mFlags{};
|
||||
uint mNumCallbackBlocks{0};
|
||||
uint mCallbackBlockBase{0};
|
||||
|
||||
struct TargetData {
|
||||
int FilterType;
|
||||
int FilterType{};
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
TargetData mDirect;
|
||||
std::array<TargetData,MAX_SENDS> mSend;
|
||||
std::array<TargetData,MaxSendCount> mSend;
|
||||
|
||||
/* The first MaxResamplerPadding/2 elements are the sample history from the
|
||||
* previous mix, with an additional MaxResamplerPadding/2 elements that are
|
||||
|
|
@ -251,11 +255,11 @@ struct Voice {
|
|||
al::vector<HistoryLine,16> mPrevSamples{2};
|
||||
|
||||
struct ChannelData {
|
||||
float mAmbiHFScale, mAmbiLFScale;
|
||||
float mAmbiHFScale{}, mAmbiLFScale{};
|
||||
BandSplitter mAmbiSplitter;
|
||||
|
||||
DirectParams mDryParams;
|
||||
std::array<SendParams,MAX_SENDS> mWetParams;
|
||||
std::array<SendParams,MaxSendCount> mWetParams;
|
||||
};
|
||||
al::vector<ChannelData> mChans{2};
|
||||
|
||||
|
|
@ -270,11 +274,9 @@ struct Voice {
|
|||
|
||||
void prepare(DeviceBase *device);
|
||||
|
||||
static void InitMixer(al::optional<std::string> resampler);
|
||||
|
||||
DEF_NEWDEL(Voice)
|
||||
static void InitMixer(std::optional<std::string> resopt);
|
||||
};
|
||||
|
||||
extern Resampler ResamplerDefault;
|
||||
inline Resampler ResamplerDefault{Resampler::Gaussian};
|
||||
|
||||
#endif /* CORE_VOICE_H */
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include <atomic>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct Voice;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
|
@ -24,8 +22,6 @@ struct VoiceChange {
|
|||
VChangeState mState{};
|
||||
|
||||
std::atomic<VoiceChange*> mNext{nullptr};
|
||||
|
||||
DEF_NEWDEL(VoiceChange)
|
||||
};
|
||||
|
||||
#endif /* VOICE_CHANGE_H */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue