update openal

This commit is contained in:
AzaezelX 2024-06-30 14:35:57 -05:00
parent 62f3b93ff9
commit 6721a6b021
287 changed files with 33851 additions and 27325 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@
#include "almalloc.h"
#include "bufferline.h"
#include "filters/splitter.h"
#include "flexarray.h"
struct FrontStablizer {

View file

@ -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, &param);
if(err == EINVAL)
#endif
err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
if(err == 0) return true;
#else
std::ignore = prio;
#endif
WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err);
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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