update OpenAL-Soft to 1.24.3.

This commit is contained in:
Sasha Szpakowski
2025-05-03 12:51:37 -03:00
parent 375c6f88cd
commit 5e4f3241ac
322 changed files with 54386 additions and 12885 deletions
+25 -45
View File
@@ -8,7 +8,6 @@
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <sstream>
@@ -16,7 +15,8 @@
#include "albit.h"
#include "alspan.h"
#include "opthelpers.h"
#include "filesystem.h"
#include "fmt/core.h"
namespace {
@@ -43,35 +43,13 @@ enum class ReaderScope {
HFMatrix,
};
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
#else
[[gnu::format(printf,2,3)]]
#endif
std::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
template<typename ...Args>
auto make_error(size_t linenum, fmt::format_string<Args...> fmt, Args&& ...args)
-> std::optional<std::string>
{
std::optional<std::string> ret;
auto &str = ret.emplace();
str.resize(256);
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);
const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)};
if(msglen >= 0 && static_cast<size_t>(msglen) >= str.size()-plen)
{
str.resize(static_cast<size_t>(msglen) + plen + 1u);
std::vsnprintf(&str[plen], str.size()-plen, fmt, args2);
}
va_end(args2);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
auto &str = ret.emplace(fmt::format("Line {}: ", linenum));
str += fmt::format(std::move(fmt), std::forward<Args>(args)...);
return ret;
}
@@ -82,7 +60,7 @@ AmbDecConf::~AmbDecConf() = default;
std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
{
std::ifstream f{std::filesystem::u8path(fname)};
fs::ifstream f{fs::u8path(fname)};
if(!f.is_open())
return std::string("Failed to open file \"")+fname+"\"";
@@ -105,7 +83,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
if(command == "/}")
{
if(scope == ReaderScope::Global)
return make_error(linenum, "Unexpected /} in global scope");
return make_error(linenum, "Unexpected /}} in global scope");
scope = ReaderScope::Global;
continue;
}
@@ -125,7 +103,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
istr >> spkr.Connection;
}
else
return make_error(linenum, "Unexpected speakers command: %s", command.c_str());
return make_error(linenum, "Unexpected speakers command: {}", command);
}
else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix)
{
@@ -168,7 +146,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
}
}
else
return make_error(linenum, "Unexpected matrix command: %s", command.c_str());
return make_error(linenum, "Unexpected matrix command: {}", command);
}
// Global scope commands
else if(command == "/description")
@@ -185,7 +163,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
return make_error(linenum, "Duplicate version definition");
istr >> Version;
if(Version != 3)
return make_error(linenum, "Unsupported version: %d", Version);
return make_error(linenum, "Unsupported version: {}", Version);
}
else if(command == "/dec/chan_mask")
{
@@ -194,7 +172,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
istr >> std::hex >> ChanMask >> std::dec;
if(!ChanMask || ChanMask > Ambi4OrderMask)
return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask);
return make_error(linenum, "Invalid chan_mask: {:#x}", ChanMask);
if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
return make_error(linenum, "FuMa not compatible with over third-order");
}
@@ -204,7 +182,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
return make_error(linenum, "Duplicate freq_bands");
istr >> FreqBands;
if(FreqBands != 1 && FreqBands != 2)
return make_error(linenum, "Invalid freq_bands: %u", FreqBands);
return make_error(linenum, "Invalid freq_bands: {}", FreqBands);
}
else if(command == "/dec/speakers")
{
@@ -213,7 +191,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
size_t numspeakers{};
istr >> numspeakers;
if(!numspeakers)
return make_error(linenum, "Invalid speakers: %zu", numspeakers);
return make_error(linenum, "Invalid speakers: {}", numspeakers);
Speakers.resize(numspeakers);
}
else if(command == "/dec/coeff_scale")
@@ -226,7 +204,7 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
else
return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str());
return make_error(linenum, "Unexpected coeff_scale: {}", scale);
if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
return make_error(linenum, "FuMa not compatible with over third-order");
@@ -268,8 +246,8 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
if(FreqBands == 1)
{
if(command != "/matrix/{")
return make_error(linenum, "Unexpected \"%s\" for a single-band decoder",
command.c_str());
return make_error(linenum, "Unexpected \"{}\" for a single-band decoder",
command);
scope = ReaderScope::HFMatrix;
}
else
@@ -279,15 +257,16 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
else if(command == "/hfmatrix/{")
scope = ReaderScope::HFMatrix;
else
return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder",
command.c_str());
return make_error(linenum, "Unexpected \"{}\" for a dual-band decoder",
command);
}
}
else if(command == "/end")
{
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str());
return make_error(linenum, "Extra junk on end: {}",
std::string_view{buffer}.substr(endpos));
if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size()
|| (FreqBands == 2 && lfmatrix_pos < Speakers.size()))
@@ -298,12 +277,13 @@ std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
return std::nullopt;
}
else
return make_error(linenum, "Unexpected command: %s", command.c_str());
return make_error(linenum, "Unexpected command: {}", command);
istr.clear();
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str());
return make_error(linenum, "Extra junk on line: {}",
std::string_view{buffer}.substr(endpos));
buffer.clear();
}
return make_error(linenum, "Unexpected end of file");
+12 -9
View File
@@ -10,7 +10,6 @@ namespace {
using AmbiChannelFloatArray = std::array<float,MaxAmbiChannels>;
constexpr auto inv_sqrt2f = static_cast<float>(1.0/al::numbers::sqrt2);
constexpr auto inv_sqrt3f = static_cast<float>(1.0/al::numbers::sqrt3);
@@ -76,16 +75,20 @@ static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order
* content.
*/
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},
std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f},
std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f},
std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f},
std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f},
std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f},
std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-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),
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(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch");
+27 -27
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,10 +14,10 @@ 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.
*/
inline constexpr uint8_t MaxAmbiOrder{3};
constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t
{ return (order+1) * (order+1); }
inline constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)};
inline constexpr auto MaxAmbiOrder = std::uint8_t{3};
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
@@ -39,20 +39,20 @@ inline constexpr uint AmbiPeriphonicMask{0xfe7ce4};
* 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
constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t
{ return order*2 + 1; }
inline constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)};
inline constexpr auto MaxAmbi2DChannels = Ambi2DChannelsFromOrder(MaxAmbiOrder);
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
* coefficients should be divided by these values to get proper scalings.
*/
struct AmbiScale {
static inline constexpr std::array<float,MaxAmbiChannels> FromN3D{{
static constexpr auto FromN3D = std::array<float,MaxAmbiChannels>{
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{{
};
static constexpr auto FromSN3D = std::array<float,MaxAmbiChannels>{
1.000000000f, /* ACN 0, sqrt(1) */
1.732050808f, /* ACN 1, sqrt(3) */
1.732050808f, /* ACN 2, sqrt(3) */
@@ -69,8 +69,8 @@ struct AmbiScale {
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{{
};
static constexpr auto FromFuMa = std::array<float,MaxAmbiChannels>{
1.414213562f, /* ACN 0 (W), sqrt(2) */
1.732050808f, /* ACN 1 (Y), sqrt(3) */
1.732050808f, /* ACN 2 (Z), sqrt(3) */
@@ -87,15 +87,15 @@ struct AmbiScale {
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{{
};
static constexpr auto FromUHJ = std::array<float,MaxAmbiChannels>{
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,
@@ -111,7 +111,7 @@ struct AmbiScale {
};
struct AmbiIndex {
static inline constexpr std::array<uint8_t,MaxAmbiChannels> FromFuMa{{
static constexpr auto FromFuMa = std::array<std::uint8_t,MaxAmbiChannels>{
0, /* W */
3, /* X */
1, /* Y */
@@ -128,8 +128,8 @@ struct AmbiIndex {
10, /* O */
15, /* P */
9, /* Q */
}};
static inline constexpr std::array<uint8_t,MaxAmbi2DChannels> FromFuMa2D{{
};
static constexpr auto FromFuMa2D = std::array<std::uint8_t,MaxAmbi2DChannels>{
0, /* W */
3, /* X */
1, /* Y */
@@ -137,23 +137,23 @@ struct AmbiIndex {
4, /* V */
15, /* P */
9, /* Q */
}};
};
static inline constexpr std::array<uint8_t,MaxAmbiChannels> FromACN{{
static constexpr auto FromACN = std::array<std::uint8_t,MaxAmbiChannels>{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
}};
static inline constexpr std::array<uint8_t,MaxAmbi2DChannels> FromACN2D{{
};
static constexpr auto FromACN2D = std::array<std::uint8_t,MaxAmbi2DChannels>{
0, 1,3, 4,8, 9,15
}};
};
static inline constexpr std::array<uint8_t,MaxAmbiChannels> OrderFromChannel{{
static constexpr auto OrderFromChannel = std::array<std::uint8_t,MaxAmbiChannels>{
0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
}};
static inline constexpr std::array<uint8_t,MaxAmbi2DChannels> OrderFrom2DChannel{{
};
static constexpr auto OrderFrom2DChannel = std::array<std::uint8_t,MaxAmbi2DChannels>{
0, 1,1, 2,2, 3,3,
}};
};
};
+5 -4
View File
@@ -2,7 +2,8 @@
#define CORE_EVENT_H
#include <array>
#include <stdint.h>
#include <cstdint>
#include <string>
#include <variant>
#include "almalloc.h"
@@ -12,7 +13,7 @@ struct EffectState;
using uint = unsigned int;
enum class AsyncEnableBits : uint8_t {
enum class AsyncEnableBits : std::uint8_t {
SourceState,
BufferCompleted,
Disconnected,
@@ -20,7 +21,7 @@ enum class AsyncEnableBits : uint8_t {
};
enum class AsyncSrcState : uint8_t {
enum class AsyncSrcState : std::uint8_t {
Reset,
Stop,
Play,
@@ -40,7 +41,7 @@ struct AsyncBufferCompleteEvent {
};
struct AsyncDisconnectEvent {
std::array<char,244> msg;
std::string msg;
};
struct AsyncEffectReleaseEvent {
+7 -11
View File
@@ -73,12 +73,9 @@ void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
const auto lfSamples = al::span<float>{mSamples[sLFBand]}.first(SamplesToDo);
for(auto &chandec : decoder)
{
chandec.mXOver.process({input->data(), SamplesToDo}, hfSamples, lfSamples);
MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand].data(),
chandec.mGains[sHFBand].data(), 0, 0);
MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand].data(),
chandec.mGains[sLFBand].data(), 0, 0);
++input;
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);
}
};
auto decode_singleband = [=](std::vector<ChannelDecoderSingle> &decoder)
@@ -86,9 +83,8 @@ void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
auto input = InSamples.cbegin();
for(auto &chandec : decoder)
{
MixSamples(al::span{*input}.first(SamplesToDo), OutBuffer, chandec.mGains.data(),
chandec.mGains.data(), 0, 0);
++input;
MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains,
chandec.mGains, 0, 0);
}
};
@@ -106,8 +102,8 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
*/
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};
const auto mid = al::span{mStablizer->MidDirect}.first(SamplesToDo);
const auto side = al::span{mStablizer->Side}.first(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);
+2 -1
View File
@@ -13,11 +13,12 @@
#include "devformat.h"
#include "filters/splitter.h"
#include "front_stablizer.h"
#include "opthelpers.h"
using ChannelDec = std::array<float,MaxAmbiChannels>;
class BFormatDec {
class SIMDALIGN BFormatDec {
static constexpr size_t sHFBand{0};
static constexpr size_t sLFBand{1};
static constexpr size_t sNumBands{2};
+27 -29
View File
@@ -126,34 +126,36 @@ void bs2b::clear()
history.fill(bs2b::t_last_sample{});
}
void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo)
void bs2b::cross_feed(const al::span<float> Left, const al::span<float> Right)
{
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};
const auto a0lo = a0_lo;
const auto b1lo = b1_lo;
const auto a0hi = a0_hi;
const auto a1hi = a1_hi;
const auto b1hi = b1_hi;
auto lsamples = Left.first(std::min(Left.size(), Right.size()));
auto rsamples = Right.first(lsamples.size());
auto samples = std::array<std::array<float,2>,128>{};
while(!lsamples.empty())
auto leftio = lsamples.begin();
auto rightio = rsamples.begin();
while(auto rem = std::distance(leftio, lsamples.end()))
{
const size_t todo{std::min(samples.size(), lsamples.size())};
const auto todo = std::min<ptrdiff_t>(samples.size(), rem);
/* Process left input */
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>
auto z_lo = history[0].lo;
auto z_hi = history[0].hi;
std::transform(leftio, leftio+todo, samples.begin(),
[a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) noexcept
{
float y0{a0hi*x + z_hi};
const auto y0 = a0hi*x + z_hi;
z_hi = a1hi*x + b1hi*y0;
float y1{a0lo*x + z_lo};
const auto y1 = a0lo*x + z_lo;
z_lo = b1lo*y1;
return {y0, y1};
return std::array{y0, y1};
});
history[0].lo = z_lo;
history[0].hi = z_hi;
@@ -161,28 +163,24 @@ void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo)
/* Process right input */
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>
std::transform(rightio, rightio+todo, samples.cbegin(), samples.begin(),
[a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array<float,2> &out) noexcept
{
float y0{a0lo*x + z_lo};
const auto y0 = a0lo*x + z_lo;
z_lo = b1lo*y0;
float y1{a0hi*x + z_hi};
const auto y1 = a0hi*x + z_hi;
z_hi = a1hi*x + b1hi*y1;
return {out[0]+y0, out[1]+y1};
return std::array{out[0]+y0, out[1]+y1};
});
history[1].lo = z_lo;
history[1].hi = z_hi;
auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(),
leftio = std::transform(samples.cbegin(), samples.cbegin()+todo, leftio,
[](const std::array<float,2> &in) { return in[0]; });
lsamples = {iter, lsamples.end()};
iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(),
rightio = std::transform(samples.cbegin(), samples.cbegin()+todo, rightio,
[](const std::array<float,2> &in) { return in[1]; });
rsamples = {iter, rsamples.end()};
}
}
+4 -1
View File
@@ -25,6 +25,9 @@
#define CORE_BS2B_H
#include <array>
#include <cstddef>
#include "alspan.h"
namespace Bs2b {
@@ -80,7 +83,7 @@ struct bs2b {
/* Clear buffer */
void clear();
void cross_feed(float *Left, float *Right, size_t SamplesToDo);
void cross_feed(const al::span<float> Left, const al::span<float> Right);
};
} // namespace Bs2b
+108 -44
View File
@@ -7,12 +7,14 @@
#include <cmath>
#include <cstddef>
#include <limits>
#include <memory>
#include <stdexcept>
#include <vector>
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "bsinc_defs.h"
#include "opthelpers.h"
#include "resampler_limits.h"
@@ -20,10 +22,6 @@ 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.
@@ -36,7 +34,7 @@ using std::cyl_bessel_i;
* compounding the rounding and precision error), but it's good enough.
*/
template<typename T, typename U>
U cyl_bessel_i(T nu, U x)
constexpr auto cyl_bessel_i(T nu, U x) -> U
{
if(nu != T{0})
throw std::runtime_error{"cyl_bessel_i: nu != 0"};
@@ -60,7 +58,6 @@ U cyl_bessel_i(T nu, U x)
} while(sum != last_sum);
return static_cast<U>(sum);
}
#endif
/* This is the normalized cardinal sine (sinc) function.
*
@@ -93,7 +90,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_
{
if(!(k >= -1.0 && k <= 1.0))
return 0.0;
return cyl_bessel_i(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.
@@ -119,74 +116,139 @@ constexpr double CalcKaiserBeta(const double rejection)
struct BSincHeader {
double width{};
double beta{};
double scaleBase{};
double scaleLimit{};
std::array<uint,BSincScaleCount> a{};
std::array<double,BSincScaleCount> a{};
std::array<uint,BSincScaleCount> m{};
uint total_size{};
constexpr BSincHeader(uint Rejection, uint Order) noexcept
: width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)}
, scaleBase{width / 2.0}
constexpr BSincHeader(uint rejection, uint order, uint maxScale) noexcept
: beta{CalcKaiserBeta(rejection)}, scaleBase{CalcKaiserWidth(rejection, order) / 2.0}
, scaleLimit{1.0 / maxScale}
{
uint num_points{Order+1};
const auto base_a = (order+1.0) / 2.0;
for(uint si{0};si < BSincScaleCount;++si)
{
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_};
const auto scale = lerpd(scaleBase, 1.0, (si+1u) / double{BSincScaleCount});
a[si] = std::min(base_a/scale, base_a*maxScale);
/* std::ceil() isn't constexpr until C++23, this should behave the
* same.
*/
auto a_ = static_cast<uint>(a[si]);
a_ += (static_cast<double>(a_) != a[si]);
m[si] = a_ * 2u;
a[si] = a_;
total_size += 4 * BSincPhaseCount * ((m+3) & ~3u);
total_size += 4u * BSincPhaseCount * ((m[si]+3u) & ~3u);
}
}
};
/* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop
* at nyquist. Each filter will scale up the order when downsampling, to 23rd
* and 47th order respectively.
* at nyquist. Each filter will scale up to double size when downsampling, to
* 23rd and 47th order respectively.
*/
constexpr BSincHeader bsinc12_hdr{60, 11};
constexpr BSincHeader bsinc24_hdr{60, 23};
constexpr auto bsinc12_hdr = BSincHeader{60, 11, 2};
constexpr auto bsinc24_hdr = BSincHeader{60, 23, 2};
/* 47th order filter (48-point) with an 80dB drop at nyquist. The filter order
* doesn't increase when downsampling.
*/
constexpr auto bsinc48_hdr = BSincHeader{80, 47, 1};
template<const BSincHeader &hdr>
struct BSincFilterArray {
struct SIMDALIGN BSincFilterArray {
alignas(16) std::array<float, hdr.total_size> mTable{};
BSincFilterArray()
{
static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u};
static constexpr auto BSincPointsMax = (hdr.m[0]+3u) & ~3u;
static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small");
using filter_type = std::array<std::array<double,BSincPointsMax>,BSincPhaseCount>;
auto filterptr = std::make_unique<std::array<filter_type,BSincScaleCount>>();
const auto filter = filterptr->begin();
auto filter = std::vector<filter_type>(BSincScaleCount);
const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)};
static constexpr auto besseli_0_beta = ::cyl_bessel_i(0, hdr.beta);
/* Calculate the Kaiser-windowed Sinc filter coefficients for each
* scale and phase index.
*/
for(uint si{0};si < BSincScaleCount;++si)
{
const uint m{hdr.a[si] * 2};
const size_t o{(BSincPointsMax-m) / 2};
const double scale{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};
const auto a = hdr.a[si];
const auto m = hdr.m[si];
const auto l = std::floor(m*0.5) - 1.0;
const auto o = size_t{BSincPointsMax-m} / 2u;
const auto scale = lerpd(hdr.scaleBase, 1.0, (si+1u) / double{BSincScaleCount});
/* Calculate an appropriate cutoff frequency. An explanation may be
* in order here.
*
* When up-sampling, or down-sampling by less than the max scaling
* factor (when scale >= scaleLimit), the filter order increases as
* the down-sampling factor is reduced, enabling a consistent
* filter response output.
*
* When down-sampling by more than the max scale factor, the filter
* order stays constant to avoid further increasing the processing
* cost, causing the transition width to increase. This would
* normally be compensated for by reducing the cutoff frequency,
* to keep the transition band under the nyquist frequency and
* avoid aliasing. However, this has the side-effect of attenuating
* more of the original high frequency content, which can be
* significant with more extreme down-sampling scales.
*
* To combat this, we can allow for some aliasing to keep the
* cutoff frequency higher than it would otherwise be. We can allow
* the transition band to "wrap around" the nyquist frequency, so
* the output would have some low-level aliasing that overlays with
* the attenuated frequencies in the transition band. This allows
* the cutoff frequency to remain fixed as the transition width
* increases, until the stop frequency aliases back to the cutoff
* frequency and the transition band becomes fully wrapped over
* itself, at which point the cutoff frequency will lower at half
* the rate the transition width increases.
*
* This has an additional benefit when dealing with typical output
* rates like 44 or 48khz. Since human hearing maxes out at 20khz,
* and these rates handle frequencies up to 22 or 24khz, this lets
* some aliasing get masked. For example, the bsinc24 filter with
* 48khz output has a cutoff of 20khz when down-sampling, and a
* 4khz transition band. When down-sampling by more extreme scales,
* the cutoff frequency can stay at 20khz while the transition
* width doubles before any aliasing noise may become audible.
*
* This is what we do here.
*
* 'max_cutoff` is the upper bound normalized cutoff frequency for
* this scale factor, that aligns with the same absolute frequency
* as nominal resample factors. When up-sampling (scale == 1), the
* cutoff can't be raised further than this, or else it would
* prematurely add audible aliasing noise.
*
* 'width' is the normalized transition width for this scale
* factor.
*
* '(scale - width)*0.5' calculates the cutoff frequency necessary
* for the transition band to fully wrap on itself around the
* nyquist frequency. If this is larger than max_cutoff, the
* transition band is not fully wrapped at this scale and the
* cutoff doesn't need adjustment.
*/
const auto max_cutoff = (0.5 - hdr.scaleBase)*scale;
const auto width = hdr.scaleBase * std::max(hdr.scaleLimit, scale);
const auto cutoff2 = std::min(max_cutoff, (scale - width)*0.5) * 2.0;
for(uint pi{0};pi < BSincPhaseCount;++pi)
{
const double phase{std::floor(l) + (pi/double{BSincPhaseCount})};
const auto phase = 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, besseli_0_beta) * cutoff *
Sinc(cutoff*x);
const auto x = static_cast<double>(i) - phase;
filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, besseli_0_beta) * cutoff2 *
Sinc(cutoff2*x);
}
}
}
@@ -194,8 +256,8 @@ struct BSincFilterArray {
size_t idx{0};
for(size_t si{0};si < BSincScaleCount;++si)
{
const size_t m{((hdr.a[si]*2) + 3) & ~3u};
const size_t o{(BSincPointsMax-m) / 2};
const auto m = (hdr.m[si]+3_uz) & ~3_uz;
const auto o = size_t{BSincPointsMax-m} / 2u;
/* Write out each phase index's filter and phase delta for this
* quality scale.
@@ -279,11 +341,12 @@ struct BSincFilterArray {
}
[[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; }
[[nodiscard]] constexpr auto getTable() const noexcept -> const float* { return mTable.data(); }
[[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; }
};
const BSincFilterArray<bsinc12_hdr> bsinc12_filter{};
const BSincFilterArray<bsinc24_hdr> bsinc24_filter{};
const auto bsinc12_filter = BSincFilterArray<bsinc12_hdr>{};
const auto bsinc24_filter = BSincFilterArray<bsinc24_hdr>{};
const auto bsinc48_filter = BSincFilterArray<bsinc48_hdr>{};
template<typename T>
constexpr BSincTable GenerateBSincTable(const T &filter)
@@ -293,7 +356,7 @@ constexpr BSincTable GenerateBSincTable(const T &filter)
ret.scaleBase = static_cast<float>(hdr.scaleBase);
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.m[i] = (hdr.m[i]+3u) & ~3u;
ret.filterOffset[0] = 0;
for(size_t i{1};i < BSincScaleCount;++i)
ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount;
@@ -305,3 +368,4 @@ constexpr BSincTable GenerateBSincTable(const T &filter)
const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)};
const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)};
const BSincTable gBSinc48{GenerateBSincTable(bsinc48_filter)};
+6 -4
View File
@@ -3,17 +3,19 @@
#include <array>
#include "alspan.h"
#include "bsinc_defs.h"
#include "opthelpers.h"
struct BSincTable {
float scaleBase, scaleRange;
std::array<unsigned int,BSincScaleCount> m;
std::array<unsigned int,BSincScaleCount> filterOffset;
const float *Tab;
al::span<const float> Tab;
};
extern const BSincTable gBSinc12;
extern const BSincTable gBSinc24;
DECL_HIDDEN extern const BSincTable gBSinc12;
DECL_HIDDEN extern const BSincTable gBSinc24;
DECL_HIDDEN extern const BSincTable gBSinc48;
#endif /* CORE_BSINC_TABLES_H */
+1 -3
View File
@@ -1,10 +1,8 @@
#ifndef CORE_BUFFER_STORAGE_H
#define CORE_BUFFER_STORAGE_H
#include <atomic>
#include <cstddef>
#include "alnumeric.h"
#include "alspan.h"
#include "ambidefs.h"
#include "storage_formats.h"
@@ -34,7 +32,7 @@ constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept
}
using CallbackType = int(*)(void*, void*, int);
using CallbackType = int(*)(void*, void*, int) noexcept;
struct BufferStorage {
CallbackType mCallback{nullptr};
+13 -20
View File
@@ -27,29 +27,22 @@ ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
ContextBase::~ContextBase()
{
if(auto curarray = mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed))
std::destroy_n(curarray->end(), curarray->size());
mActiveAuxSlots.store(nullptr, std::memory_order_relaxed);
mVoices.store(nullptr, std::memory_order_relaxed);
if(mAsyncEvents)
{
size_t count{0};
auto evt_vec = mAsyncEvents->getReadVector();
if(evt_vec.first.len > 0)
for(auto &evt : mAsyncEvents->getReadVector())
{
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)
{
std::destroy_n(std::launder(reinterpret_cast<AsyncEvent*>(evt_vec.second.buf)),
evt_vec.second.len);
count += evt_vec.second.len;
if(evt.len > 0)
{
std::destroy_n(std::launder(reinterpret_cast<AsyncEvent*>(evt.buf)), evt.len);
count += evt.len;
}
}
if(count > 0)
TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s");
TRACE("Destructed {} orphaned event{}", count, (count==1)?"":"s");
mAsyncEvents->readAdvance(count);
}
}
@@ -74,7 +67,7 @@ void ContextBase::allocVoiceProps()
{
static constexpr size_t clustersize{std::tuple_size_v<VoicePropsCluster::element_type>};
TRACE("Increasing allocated voice properties to %zu\n",
TRACE("Increasing allocated voice properties to {}",
(mVoicePropClusters.size()+1) * clustersize);
auto clusterptr = std::make_unique<VoicePropsCluster::element_type>();
@@ -106,7 +99,7 @@ void ContextBase::allocVoices(size_t 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);
TRACE("Increasing allocated voices to {}", totalcount);
while(addcount)
{
@@ -129,7 +122,7 @@ void ContextBase::allocEffectSlotProps()
{
static constexpr size_t clustersize{std::tuple_size_v<EffectSlotPropsCluster::element_type>};
TRACE("Increasing allocated effect slot properties to %zu\n",
TRACE("Increasing allocated effect slot properties to {}",
(mEffectSlotPropClusters.size()+1) * clustersize);
auto clusterptr = std::make_unique<EffectSlotPropsCluster::element_type>();
@@ -159,7 +152,7 @@ EffectSlot *ContextBase::getEffectSlot()
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) * clusterptr->size()};
TRACE("Increasing allocated effect slots to %zu\n", totalcount);
TRACE("Increasing allocated effect slots to {}", totalcount);
mEffectSlotClusters.emplace_back(std::move(clusterptr));
return mEffectSlotClusters.back()->data();
@@ -170,7 +163,7 @@ void ContextBase::allocContextProps()
{
static constexpr size_t clustersize{std::tuple_size_v<ContextPropsCluster::element_type>};
TRACE("Increasing allocated context properties to %zu\n",
TRACE("Increasing allocated context properties to {}",
(mContextPropClusters.size()+1) * clustersize);
auto clusterptr = std::make_unique<ContextPropsCluster::element_type>();
+15 -7
View File
@@ -1,6 +1,8 @@
#ifndef CORE_CONTEXT_H
#define CORE_CONTEXT_H
#include "config.h"
#include <array>
#include <atomic>
#include <bitset>
@@ -9,7 +11,6 @@
#include <thread>
#include <vector>
#include "almalloc.h"
#include "alsem.h"
#include "alspan.h"
#include "async_event.h"
@@ -53,19 +54,22 @@ struct ContextProps {
float DopplerFactor;
float DopplerVelocity;
float SpeedOfSound;
#if ALSOFT_EAX
float DistanceFactor;
#endif
bool SourceDistanceModel;
DistanceModel mDistanceModel;
std::atomic<ContextProps*> next;
std::atomic<ContextProps*> next{};
};
struct ContextParams {
/* Pointer to the most recent property values that are awaiting an update. */
std::atomic<ContextProps*> ContextUpdate{nullptr};
alu::Vector Position{};
alu::Vector Position;
alu::Matrix Matrix{alu::Matrix::Identity()};
alu::Vector Velocity{};
alu::Vector Velocity;
float Gain{1.0f};
float MetersPerUnit{1.0f};
@@ -113,7 +117,7 @@ struct ContextBase {
ContextParams mParams;
using VoiceArray = al::FlexArray<Voice*>;
al::atomic_unique_ptr<VoiceArray> mVoices{};
al::atomic_unique_ptr<VoiceArray> mVoices;
std::atomic<size_t> mActiveVoiceCount{};
void allocVoices(size_t addcount);
@@ -130,6 +134,10 @@ struct ContextBase {
using EffectSlotArray = al::FlexArray<EffectSlot*>;
/* 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;
@@ -168,10 +176,10 @@ struct ContextBase {
std::vector<ContextPropsCluster> mContextPropClusters;
ContextBase(DeviceBase *device);
explicit ContextBase(DeviceBase *device);
ContextBase(const ContextBase&) = delete;
ContextBase& operator=(const ContextBase&) = delete;
~ContextBase();
virtual ~ContextBase();
};
#endif /* CORE_CONTEXT_H */
+10 -8
View File
@@ -169,10 +169,11 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::s
SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans,
uint srcRate, uint dstRate, Resampler resampler)
{
SampleConverterPtr converter;
if(numchans < 1 || srcRate < 1 || dstRate < 1)
return nullptr;
return converter;
SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}};
converter = SampleConverterPtr{new(FamCount(numchans)) SampleConverter{numchans}};
converter->mSrcType = srcType;
converter->mDstType = dstType;
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
@@ -189,8 +190,11 @@ SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstTyp
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);
@@ -291,8 +295,7 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f);
/* Now resample, and store the result in the output buffer. */
mResample(&mState, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac,
increment, DstData.first(DstSize));
mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize));
StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType);
}
@@ -387,8 +390,7 @@ uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *con
std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f);
/* Now resample, and store the result in the output buffer. */
mResample(&mState, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac,
increment, DstData.first(DstSize));
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});
+2 -2
View File
@@ -24,7 +24,7 @@ struct SampleConverter {
uint mFracOffset{};
uint mIncrement{};
InterpState mState{};
InterpState mState;
ResamplerFunc mResample{};
alignas(16) FloatBufferLine mSrcSamples{};
@@ -35,7 +35,7 @@ struct SampleConverter {
};
al::FlexArray<ChanSamples> mChan;
SampleConverter(size_t numchans) : mChan{numchans} { }
explicit SampleConverter(size_t numchans) : mChan{numchans} { }
[[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;
+6 -7
View File
@@ -1,5 +1,6 @@
#include "config.h"
#include "config_simd.h"
#include "cpu_caps.h"
@@ -23,8 +24,6 @@
#include <string>
int CPUCapFlags{0};
namespace {
#if defined(HAVE_GCC_GET_CPUID) \
@@ -111,22 +110,22 @@ std::optional<CPUInfo> GetCPUInfo()
#else
/* Assume support for whatever's supported if we can't check for it */
#if defined(HAVE_SSE4_1)
#if HAVE_SSE4_1
#warning "Assuming SSE 4.1 run-time support!"
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
#elif defined(HAVE_SSE3)
#elif HAVE_SSE3
#warning "Assuming SSE 3 run-time support!"
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
#elif defined(HAVE_SSE2)
#elif HAVE_SSE2
#warning "Assuming SSE 2 run-time support!"
ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2;
#elif defined(HAVE_SSE)
#elif HAVE_SSE
#warning "Assuming SSE run-time support!"
ret.mCaps |= CPU_CAP_SSE;
#endif
#endif /* CAN_GET_CPUID */
#ifdef HAVE_NEON
#if HAVE_NEON
#ifdef __ARM_NEON
ret.mCaps |= CPU_CAP_NEON;
#elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64))
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string>
extern int CPUCapFlags;
inline int CPUCapFlags{0};
enum {
CPU_CAP_SSE = 1<<0,
CPU_CAP_SSE2 = 1<<1,
+43 -10
View File
@@ -9,16 +9,15 @@
#include "alnumeric.h"
#include "cubic_defs.h"
/* These 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.
/* 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
* Additional changes were made here, the most obvious being that it has full
* floating-point precision instead of 11-bit fixed-point, but also an offset
* adjustment for the phase coefficients to more cleanly transition from the
* end of one sample set to the start of the next.
* adjustment for the coefficients to better preserve phase.
*/
namespace {
@@ -27,9 +26,9 @@ 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};
const double s{ std::sin(al::numbers::pi*1.280/1024.0 * k)};
const double t{(std::cos(al::numbers::pi*2.000/1023.0 * k) - 1.0) * 0.50};
const double u{(std::cos(al::numbers::pi*4.000/1023.0 * k) - 1.0) * 0.08};
return s * (t + u + 1.0) / k;
}
@@ -69,13 +68,47 @@ GaussianTable::GaussianTable()
mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3];
}
SplineTable::SplineTable()
{
static constexpr auto third = 1.0/3.0;
static constexpr auto sixth = 1.0/6.0;
/* 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 auto mu = static_cast<double>(pi) / double{CubicPhaseCount};
const auto mu2 = mu*mu;
const auto mu3 = mu*mu2;
mTable[pi].mCoeffs[0] = static_cast<float>( -third*mu + 0.5*mu2 - sixth*mu3);
mTable[pi].mCoeffs[1] = static_cast<float>(1.0 - 0.5*mu - mu2 + 0.5*mu3);
mTable[pi].mCoeffs[2] = static_cast<float>( mu + 0.5*mu2 - 0.5*mu3);
mTable[pi].mCoeffs[3] = static_cast<float>( -sixth*mu + sixth*mu3);
}
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];
}
static constexpr auto pi = std::size_t{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;++i)
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)};
+5 -1
View File
@@ -5,15 +5,19 @@
#include <cstddef>
#include "cubic_defs.h"
#include "opthelpers.h"
struct CubicTable {
struct SIMDALIGN CubicTable {
std::array<CubicCoefficients,CubicPhaseCount> mTable{};
};
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};
+3 -3
View File
@@ -3,7 +3,7 @@
#include "dbus_wrap.h"
#ifdef HAVE_DYNLOAD
#if HAVE_DYNLOAD
#include <mutex>
#include <type_traits>
@@ -18,7 +18,7 @@ void PrepareDBus()
dbus_handle = LoadLib(libname);
if(!dbus_handle)
{
WARN("Failed to load %s\n", libname);
WARN("Failed to load {}", libname);
return;
}
@@ -28,7 +28,7 @@ void PrepareDBus()
load_func(p##x, #x); \
if(!p##x) \
{ \
WARN("Failed to load function %s\n", #x); \
WARN("Failed to load function {}", #x); \
CloseLib(dbus_handle); \
dbus_handle = nullptr; \
return; \
+9 -2
View File
@@ -7,7 +7,7 @@
#include "dynload.h"
#ifdef HAVE_DYNLOAD
#if HAVE_DYNLOAD
#include <mutex>
@@ -63,16 +63,23 @@ inline auto HasDBus()
#else
constexpr bool HasDBus() noexcept { return true; }
#endif /* HAVE_DYNLOAD */
#endif
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{};
};
+27 -20
View File
@@ -3,6 +3,11 @@
#include "devformat.h"
#include <string_view>
namespace {
using namespace std::string_view_literals;
} // namespace
uint BytesFromDevFmt(DevFmtType type) noexcept
{
@@ -29,39 +34,41 @@ 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);
}
return 0;
}
const char *DevFmtTypeString(DevFmtType type) noexcept
auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view
{
switch(type)
{
case DevFmtByte: return "Int8";
case DevFmtUByte: return "UInt8";
case DevFmtShort: return "Int16";
case DevFmtUShort: return "UInt16";
case DevFmtInt: return "Int32";
case DevFmtUInt: return "UInt32";
case DevFmtFloat: return "Float32";
case DevFmtByte: return "Int8"sv;
case DevFmtUByte: return "UInt8"sv;
case DevFmtShort: return "Int16"sv;
case DevFmtUShort: return "UInt16"sv;
case DevFmtInt: return "Int32"sv;
case DevFmtUInt: return "UInt32"sv;
case DevFmtFloat: return "Float32"sv;
}
return "(unknown type)";
return "(unknown type)"sv;
}
const char *DevFmtChannelsString(DevFmtChannels chans) noexcept
auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view
{
switch(chans)
{
case DevFmtMono: return "Mono";
case DevFmtStereo: return "Stereo";
case DevFmtQuad: return "Quadraphonic";
case DevFmtX51: return "5.1 Surround";
case DevFmtX61: return "6.1 Surround";
case DevFmtX71: return "7.1 Surround";
case DevFmtX714: return "7.1.4 Surround";
case DevFmtX3D71: return "3D7.1 Surround";
case DevFmtAmbi3D: return "Ambisonic 3D";
case DevFmtMono: return "Mono"sv;
case DevFmtStereo: return "Stereo"sv;
case DevFmtQuad: return "Quadraphonic"sv;
case DevFmtX51: return "5.1 Surround"sv;
case DevFmtX61: return "6.1 Surround"sv;
case DevFmtX71: return "7.1 Surround"sv;
case DevFmtX714: return "7.1.4 Surround"sv;
case DevFmtX7144: return "7.1.4.4 Surround"sv;
case DevFmtX3D71: return "3D7.1 Surround"sv;
case DevFmtAmbi3D: return "Ambisonic 3D"sv;
}
return "(unknown channels)";
return "(unknown channels)"sv;
}
+10 -3
View File
@@ -3,6 +3,7 @@
#include <cstdint>
#include <cstddef>
#include <string_view>
using uint = unsigned int;
@@ -26,6 +27,11 @@ enum Channel : unsigned char {
TopBackCenter,
TopBackRight,
BottomFrontLeft,
BottomFrontRight,
BottomBackLeft,
BottomBackRight,
Aux0,
Aux1,
Aux2,
@@ -67,12 +73,13 @@ enum DevFmtChannels : unsigned char {
DevFmtX61,
DevFmtX71,
DevFmtX714,
DevFmtX7144,
DevFmtX3D71,
DevFmtAmbi3D,
DevFmtChannelsDefault = DevFmtStereo
};
inline constexpr size_t MaxOutputChannels{16};
inline constexpr std::size_t MaxOutputChannels{16};
/* DevFmtType traits, providing the type, etc given a DevFmtType. */
template<DevFmtType T>
@@ -102,8 +109,8 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept;
inline uint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, uint ambiorder) noexcept
{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); }
const char *DevFmtTypeString(DevFmtType type) noexcept;
const char *DevFmtChannelsString(DevFmtChannels chans) noexcept;
auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view;
auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view;
enum class DevAmbiLayout : bool {
FuMa,
-3
View File
@@ -9,9 +9,6 @@
#include "mastering.h"
static_assert(std::atomic<std::chrono::nanoseconds>::is_always_lock_free);
DeviceBase::DeviceBase(DeviceType type)
: Type{type}, mContexts{al::FlexArray<ContextBase*>::Create(0)}
{
+66 -57
View File
@@ -5,9 +5,9 @@
#include <atomic>
#include <bitset>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "almalloc.h"
@@ -18,6 +18,7 @@
#include "devformat.h"
#include "filters/nfc.h"
#include "flexarray.h"
#include "fmt/core.h"
#include "intrusive_ptr.h"
#include "mixer/hrtfdefs.h"
#include "opthelpers.h"
@@ -37,28 +38,28 @@ struct HrtfStore;
using uint = unsigned int;
inline constexpr size_t MinOutputRate{8000};
inline constexpr size_t MaxOutputRate{192000};
inline constexpr size_t DefaultOutputRate{48000};
inline constexpr std::size_t MinOutputRate{8000};
inline constexpr std::size_t MaxOutputRate{192000};
inline constexpr std::size_t DefaultOutputRate{48000};
inline constexpr size_t DefaultUpdateSize{960}; /* 20ms */
inline constexpr size_t DefaultNumUpdates{3};
inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */
inline constexpr std::size_t DefaultNumUpdates{3};
enum class DeviceType : uint8_t {
enum class DeviceType : std::uint8_t {
Playback,
Capture,
Loopback
};
enum class RenderMode : uint8_t {
enum class RenderMode : std::uint8_t {
Normal,
Pairwise,
Hrtf
};
enum class StereoEncoding : uint8_t {
enum class StereoEncoding : std::uint8_t {
Basic,
Uhj,
Hrtf,
@@ -80,23 +81,23 @@ struct DistanceComp {
static constexpr uint MaxDelay{1024};
struct ChanData {
al::span<float> Buffer{}; /* Valid size is [0...MaxDelay). */
al::span<float> Buffer; /* Valid size is [0...MaxDelay). */
float Gain{1.0f};
};
std::array<ChanData,MaxOutputChannels> mChannels;
al::FlexArray<float,16> mSamples;
DistanceComp(size_t count) : mSamples{count} { }
explicit 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 uint8_t InvalidChannelIndex{static_cast<uint8_t>(~0u)};
constexpr auto InvalidChannelIndex = static_cast<std::uint8_t>(~0u);
struct BFChannelConfig {
float Scale;
@@ -120,18 +121,18 @@ struct MixParams {
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)
{
uint8_t idx{InvalidChannelIndex};
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<uint8_t>(j);
idx = static_cast<std::uint8_t>(j);
gain = AmbiMap[j].Scale * gainbase;
break;
}
@@ -143,7 +144,7 @@ struct MixParams {
struct RealMixParams {
al::span<const InputRemixMap> RemixMap;
std::array<uint8_t,MaxChannels> ChannelIndex{};
std::array<std::uint8_t,MaxChannels> ChannelIndex{};
al::span<FloatBufferLine> Buffer;
};
@@ -173,19 +174,22 @@ enum {
DeviceFlagsCount
};
enum class DeviceState : uint8_t {
enum class DeviceState : std::uint8_t {
Unprepared,
Configured,
Playing
};
struct DeviceBase {
/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */
struct SIMDALIGN DeviceBase {
std::atomic<bool> Connected{true};
const DeviceType Type{};
uint Frequency{};
uint UpdateSize{};
uint BufferSize{};
std::string mDeviceName;
uint mSampleRate{};
uint mUpdateSize{};
uint mBufferSize{};
DevFmtChannels FmtChans{};
DevFmtType FmtType{};
@@ -199,10 +203,8 @@ struct DeviceBase {
DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
std::string DeviceName;
// Device flags
std::bitset<DeviceFlagsCount> Flags{};
std::bitset<DeviceFlagsCount> Flags;
DeviceState mDeviceState{DeviceState::Unprepared};
uint NumAuxSends{};
@@ -220,16 +222,21 @@ struct DeviceBase {
*/
NfcFilter mNFCtrlFilter{};
using seconds32 = std::chrono::duration<int32_t>;
using nanoseconds32 = std::chrono::duration<int32_t, std::nano>;
std::atomic<uint> mSamplesDone{0u};
std::atomic<std::chrono::nanoseconds> mClockBase{std::chrono::nanoseconds{}};
/* Split the clock to avoid a 64-bit atomic for certain 32-bit targets. */
std::atomic<seconds32> mClockBaseSec{seconds32{}};
std::atomic<nanoseconds32> mClockBaseNSec{nanoseconds32{}};
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};
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{};
@@ -288,11 +295,6 @@ struct DeviceBase {
al::atomic_unique_ptr<al::FlexArray<ContextBase*>> mContexts;
DeviceBase(DeviceType type);
DeviceBase(const DeviceBase&) = delete;
DeviceBase& operator=(const DeviceBase&) = delete;
~DeviceBase();
[[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(); }
@@ -314,9 +316,8 @@ struct DeviceBase {
/* 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};
const auto oldCount = mMixCount.fetch_add(1u, std::memory_order_acq_rel);
return MixLock{this, oldCount+2};
}
/** Waits for the mixer to not be mixing or updating the clock. */
@@ -337,39 +338,47 @@ struct DeviceBase {
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency;
return mClockBase.load(std::memory_order_relaxed) + ns;
auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / mSampleRate;
return nanoseconds{mClockBaseNSec.load(std::memory_order_relaxed)}
+ mClockBaseSec.load(std::memory_order_relaxed) + ns;
}
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);
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 size_t SamplesToDo)
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(const al::span<void*> outBuffers, const uint numSamples);
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 __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
#else
[[gnu::format(printf,2,3)]]
#endif
void handleDisconnect(const char *msg, ...);
void doDisconnect(std::string msg);
template<typename ...Args>
void handleDisconnect(fmt::format_string<Args...> fmt, Args&& ...args)
{ doDisconnect(fmt::format(std::move(fmt), std::forward<Args>(args)...)); }
/**
* Returns the index for the given channel name (e.g. FrontCenter), or
* InvalidChannelIndex if it doesn't exist.
*/
[[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> uint8_t
[[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t
{ return RealOut.ChannelIndex[chan]; }
private:
uint renderSamples(const uint numSamples);
protected:
explicit DeviceBase(DeviceType type);
~DeviceBase();
public:
DeviceBase(const DeviceBase&) = delete;
DeviceBase& operator=(const DeviceBase&) = delete;
};
/* Must be less than 15 characters (16 including terminating null) for
+12 -18
View File
@@ -8,6 +8,7 @@
#include "alspan.h"
#include "core/bufferline.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
struct BufferStorage;
struct ContextBase;
@@ -100,15 +101,6 @@ struct ChorusProps {
float Delay;
};
struct FlangerProps {
ChorusWaveform Waveform;
int Phase;
float Rate;
float Depth;
float Feedback;
float Delay;
};
struct CompressorProps {
bool OnOff;
};
@@ -170,11 +162,9 @@ struct VmorpherProps {
VMorpherWaveform Waveform;
};
struct DedicatedDialogProps {
float Gain;
};
struct DedicatedLfeProps {
struct DedicatedProps {
enum TargetType : bool { Dialog, Lfe };
TargetType Target;
float Gain;
};
@@ -187,7 +177,6 @@ using EffectProps = std::variant<std::monostate,
ReverbProps,
AutowahProps,
ChorusProps,
FlangerProps,
CompressorProps,
DistortionProps,
EchoProps,
@@ -196,8 +185,7 @@ using EffectProps = std::variant<std::monostate,
ModulatorProps,
PshifterProps,
VmorpherProps,
DedicatedDialogProps,
DedicatedLfeProps,
DedicatedProps,
ConvolutionProps>;
@@ -206,7 +194,7 @@ struct EffectTarget {
RealMixParams *RealOut;
};
struct EffectState : public al::intrusive_ref<EffectState> {
struct SIMDALIGN EffectState : public al::intrusive_ref<EffectState> {
al::span<FloatBufferLine> mOutTarget;
@@ -221,8 +209,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;
};
+1 -4
View File
@@ -11,8 +11,5 @@
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.
*/
return std::unique_ptr<EffectSlotArray>{new(FamCount{count*2}) EffectSlotArray(count)};
return std::unique_ptr<EffectSlotArray>{new(FamCount{count}) EffectSlotArray(count)};
}
+11 -14
View File
@@ -4,7 +4,6 @@
#include <atomic>
#include <memory>
#include "almalloc.h"
#include "device.h"
#include "effects/base.h"
#include "flexarray.h"
@@ -20,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 {
@@ -46,7 +43,7 @@ struct EffectSlotProps {
al::intrusive_ptr<EffectState> State;
std::atomic<EffectSlotProps*> next;
std::atomic<EffectSlotProps*> next{};
};
@@ -67,7 +64,7 @@ struct EffectSlot {
EffectSlot *Target{nullptr};
EffectSlotType EffectType{EffectSlotType::None};
EffectProps mEffectProps{};
EffectProps mEffectProps;
al::intrusive_ptr<EffectState> mEffectState;
float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
-21
View File
@@ -3,30 +3,9 @@
#include "except.h"
#include <cstdio>
#include <cstdarg>
#include "opthelpers.h"
namespace al {
base_exception::~base_exception() = default;
void base_exception::setMessage(const char *msg, std::va_list args)
{
/* 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(mMessage.data(), mMessage.length(), msg, args2);
mMessage.pop_back();
}
va_end(args2);
/* NOLINTEND(*-array-to-pointer-decay) */
}
} // namespace al
+8 -9
View File
@@ -1,10 +1,9 @@
#ifndef CORE_EXCEPT_H
#define CORE_EXCEPT_H
#include <cstdarg>
#include <exception>
#include <string>
#include <utility>
#include <type_traits>
namespace al {
@@ -12,20 +11,20 @@ namespace al {
class base_exception : public std::exception {
std::string mMessage;
protected:
auto setMessage(const char *msg, std::va_list args) -> void;
public:
base_exception() = default;
template<typename T, std::enable_if_t<std::is_constructible_v<std::string,T>,bool> = true>
explicit base_exception(T&& msg) : mMessage{std::forward<T>(msg)} { }
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 */
+1 -1
View File
@@ -120,7 +120,7 @@ public:
const al::span<Real> dst);
/* Rather hacky. It's just here to support "manual" processing. */
[[nodiscard]] auto getComponents() const noexcept -> std::pair<Real,Real> { return {mZ1, mZ2}; }
[[nodiscard]] auto getComponents() const noexcept -> std::array<Real,2> { return {{mZ1,mZ2}}; }
void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
[[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real
{
+58 -63
View File
@@ -5,8 +5,6 @@
#include <algorithm>
#include "opthelpers.h"
/* Near-field control filters are the basis for handling the near-field effect.
* The near-field effect is a bass-boost present in the directional components
@@ -48,29 +46,26 @@
namespace {
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}
};
constexpr auto B1 = std::array{ 1.0f};
constexpr auto B2 = std::array{ 3.0f, 3.0f};
constexpr auto B3 = std::array{3.6778f, 6.4595f, 2.3222f};
constexpr auto B4 = std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f};
NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
{
NfcFilter1 nfc{};
auto nfc = NfcFilter1{};
/* Calculate bass-cut coefficients. */
float r{0.5f * w1};
float b_00{B[1][0] * r};
float g_0{1.0f + b_00};
auto r = 0.5f * w1;
auto b_00 = B1[0] * r;
auto g_0 = 1.0f + b_00;
nfc.base_gain = 1.0f / g_0;
nfc.a1 = 2.0f * b_00 / g_0;
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_00 = B[1][0] * r;
b_00 = B1[0] * r;
g_0 = 1.0f + b_00;
nfc.gain = nfc.base_gain * g_0;
@@ -81,9 +76,9 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_00{B[1][0] * r};
const float g_0{1.0f + b_00};
const auto r = 0.5f * w0;
const auto b_00 = B1[0] * r;
const auto g_0 = 1.0f + b_00;
nfc->gain = nfc->base_gain * g_0;
nfc->b1 = 2.0f * b_00 / g_0;
@@ -92,13 +87,13 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
{
NfcFilter2 nfc{};
auto nfc = NfcFilter2{};
/* Calculate bass-cut coefficients. */
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};
auto r = 0.5f * w1;
auto b_10 = B2[0] * r;
auto b_11 = B2[1] * (r*r);
auto 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;
@@ -106,8 +101,8 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[2][0] * r;
b_11 = B[2][1] * r * r;
b_10 = B2[0] * r;
b_11 = B2[1] * r * r;
g_1 = 1.0f + b_10 + b_11;
nfc.gain = nfc.base_gain * g_1;
@@ -119,10 +114,10 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[2][0] * r};
const float b_11{B[2][1] * r * r};
const float g_1{1.0f + b_10 + b_11};
const auto r = 0.5f * w0;
const auto b_10 = B2[0] * r;
const auto b_11 = B2[1] * (r*r);
const auto g_1 = 1.0f + b_10 + b_11;
nfc->gain = nfc->base_gain * g_1;
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
@@ -132,15 +127,15 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
{
NfcFilter3 nfc{};
auto nfc = NfcFilter3{};
/* Calculate bass-cut coefficients. */
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};
auto r = 0.5f * w1;
auto b_10 = B3[0] * r;
auto b_11 = B3[1] * (r*r);
auto b_00 = B3[2] * r;
auto g_1 = 1.0f + b_10 + b_11;
auto 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;
@@ -149,9 +144,9 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[3][0] * r;
b_11 = B[3][1] * r * r;
b_00 = B[3][2] * r;
b_10 = B3[0] * r;
b_11 = B3[1] * (r*r);
b_00 = B3[2] * r;
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00;
@@ -165,12 +160,12 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[3][0] * r};
const float b_11{B[3][1] * r * r};
const float b_00{B[3][2] * r};
const float g_1{1.0f + b_10 + b_11};
const float g_0{1.0f + b_00};
const auto r = 0.5f * w0;
const auto b_10 = B3[0] * r;
const auto b_11 = B3[1] * (r*r);
const auto b_00 = B3[2] * r;
const auto g_1 = 1.0f + b_10 + b_11;
const auto g_0 = 1.0f + b_00;
nfc->gain = nfc->base_gain * (g_1 * g_0);
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
@@ -181,16 +176,16 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
{
NfcFilter4 nfc{};
auto nfc = NfcFilter4{};
/* Calculate bass-cut coefficients. */
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};
auto r = 0.5f * w1;
auto b_10 = B4[0] * r;
auto b_11 = B4[1] * (r*r);
auto b_00 = B4[2] * r;
auto b_01 = B4[3] * (r*r);
auto g_1 = 1.0f + b_10 + b_11;
auto 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;
@@ -200,10 +195,10 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[4][0] * r;
b_11 = B[4][1] * r * r;
b_00 = B[4][2] * r;
b_01 = B[4][3] * r * r;
b_10 = B4[0] * r;
b_11 = B4[1] * (r*r);
b_00 = B4[2] * r;
b_01 = B4[3] * (r*r);
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00 + b_01;
@@ -218,13 +213,13 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[4][0] * r};
const float b_11{B[4][1] * r * r};
const float b_00{B[4][2] * r};
const float b_01{B[4][3] * r * r};
const float g_1{1.0f + b_10 + b_11};
const float g_0{1.0f + b_00 + b_01};
const auto r = 0.5f * w0;
const auto b_10 = B4[0] * r;
const auto b_11 = B4[1] * (r*r);
const auto b_00 = B4[2] * r;
const auto b_01 = B4[3] * (r*r);
const auto g_1 = 1.0f + b_10 + b_11;
const auto g_0 = 1.0f + b_00 + b_01;
nfc->gain = nfc->base_gain * (g_1 * g_0);
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+1 -1
View File
@@ -17,7 +17,7 @@ class BandSplitterR {
public:
BandSplitterR() = default;
BandSplitterR(const BandSplitterR&) = default;
BandSplitterR(Real f0norm) { init(f0norm); }
explicit BandSplitterR(Real f0norm) { init(f0norm); }
BandSplitterR& operator=(const BandSplitterR&) = default;
void init(Real f0norm);
-79
View File
@@ -1,79 +0,0 @@
#include "config.h"
#include "fmt_traits.h"
namespace al {
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,
-11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0
}};
const 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,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
-30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
-11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
-15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848
}};
} // namespace al
+69 -2
View File
@@ -9,8 +9,75 @@
namespace al {
extern const std::array<std::int16_t,256> muLawDecompressionTable;
extern const std::array<std::int16_t,256> aLawDecompressionTable;
inline constexpr auto muLawDecompressionTable = std::array<int16_t,256>{{
-32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
-23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
-15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
-11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0
}};
inline constexpr auto aLawDecompressionTable = std::array<int16_t,256>{{
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
-30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
-11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
-15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848
}};
template<FmtType T>
+13 -10
View File
@@ -1,24 +1,27 @@
#include "config.h"
#include "config_simd.h"
#include "fpu_ctrl.h"
#ifdef HAVE_INTRIN_H
#include <intrin.h>
#endif
#ifdef HAVE_SSE_INTRINSICS
#if HAVE_SSE_INTRINSICS
#include <emmintrin.h>
#elif defined(HAVE_SSE)
#elif HAVE_SSE
#include <xmmintrin.h>
#endif
#if defined(HAVE_SSE) && !defined(_MM_DENORMALS_ZERO_MASK)
#if 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
#if !HAVE_SSE_INTRINSICS && HAVE_SSE
#include "cpu_caps.h"
#endif
namespace {
@@ -28,14 +31,14 @@ namespace {
[[maybe_unused]]
void disable_denormals(unsigned int *state [[maybe_unused]])
{
#if defined(HAVE_SSE_INTRINSICS)
#if HAVE_SSE_INTRINSICS
*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(HAVE_SSE)
#elif HAVE_SSE
*state = _mm_getcsr();
unsigned int sseState{*state};
@@ -56,7 +59,7 @@ void disable_denormals(unsigned int *state [[maybe_unused]])
[[maybe_unused]]
void reset_fpu(unsigned int state [[maybe_unused]])
{
#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE)
#if HAVE_SSE_INTRINSICS || HAVE_SSE
_mm_setcsr(state);
#endif
}
@@ -67,9 +70,9 @@ void reset_fpu(unsigned int state [[maybe_unused]])
unsigned int FPUCtl::Set() noexcept
{
unsigned int state{};
#if defined(HAVE_SSE_INTRINSICS)
#if HAVE_SSE_INTRINSICS
disable_denormals(&state);
#elif defined(HAVE_SSE)
#elif HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
disable_denormals(&state);
#endif
@@ -78,9 +81,9 @@ unsigned int FPUCtl::Set() noexcept
void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept
{
#if defined(HAVE_SSE_INTRINSICS)
#if HAVE_SSE_INTRINSICS
reset_fpu(state);
#elif defined(HAVE_SSE)
#elif HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
reset_fpu(state);
#endif
+1 -1
View File
@@ -11,7 +11,7 @@
struct FrontStablizer {
FrontStablizer(size_t numchans) : ChannelFilters{numchans} { }
explicit FrontStablizer(size_t numchans) : ChannelFilters{numchans} { }
alignas(16) std::array<float,BufferLineSize> MidDirect{};
alignas(16) std::array<float,BufferLineSize> Side{};
+93 -80
View File
@@ -11,17 +11,18 @@
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "filesystem.h"
#include "logging.h"
#include "strutils.h"
@@ -32,46 +33,38 @@ using namespace std::string_view_literals;
std::mutex gSearchLock;
void DirectorySearch(const std::filesystem::path &path, const std::string_view ext,
void DirectorySearch(const fs::path &path, const std::string_view ext,
std::vector<std::string> *const results)
{
namespace fs = std::filesystem;
const auto base = static_cast<std::make_signed_t<size_t>>(results->size());
const auto base = results->size();
try {
auto fpath = path.lexically_normal();
if(!fs::exists(fpath))
return;
TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data());
TRACE("Searching {} for *{}", al::u8_as_char(fpath.u8string()), ext);
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());
if(fs::status(entrypath).type() != fs::file_type::regular)
continue;
const auto u8ext = entrypath.extension().u8string();
if(al::case_compare(al::u8_as_char(u8ext), ext) == 0)
results->emplace_back(al::u8_as_char(entrypath.u8string()));
}
}
catch(std::exception& e) {
ERR("Exception enumerating files: %s\n", e.what());
ERR("Exception enumerating files: {}", e.what());
}
/* HACK: Without the size check this trips up range-checked iterators, as
* al::span uses al::to_address to get the first iterator's data pointer,
* which relies on operator->(), which can assert on end iterators. The
* check shouldn't be needed with C++20's std::span.
*/
if(static_cast<size_t>(base) < results->size())
{
const al::span newlist{results->begin()+base, results->end()};
std::sort(newlist.begin(), newlist.end());
for(const auto &name : newlist)
TRACE(" got %s\n", name.c_str());
}
const auto newlist = al::span{*results}.subspan(base);
std::sort(newlist.begin(), newlist.end());
for(const auto &name : newlist)
TRACE(" got {}", name);
}
} // namespace
@@ -85,7 +78,7 @@ const PathNamePair &GetProcBinary()
{
auto get_procbin = []
{
#if !defined(ALSOFT_UWP)
#if !ALSOFT_UWP
DWORD pathlen{256};
auto fullpath = std::wstring(pathlen, L'\0');
DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)};
@@ -103,16 +96,22 @@ const PathNamePair &GetProcBinary()
}
if(len == 0)
{
ERR("Failed to get process name: error %lu\n", GetLastError());
ERR("Failed to get process name: error {}", GetLastError());
return PathNamePair{};
}
fullpath.resize(len);
#else
if(__argc < 1 || !__wargv)
{
ERR("Failed to get process name: __argc = {}, __wargv = {}", __argc,
static_cast<void*>(__wargv));
return PathNamePair{};
}
const WCHAR *exePath{__wargv[0]};
if(!exePath)
{
ERR("Failed to get process name: __wargv[0] == nullptr\n");
ERR("Failed to get process name: __wargv[0] == nullptr");
return PathNamePair{};
}
std::wstring fullpath{exePath};
@@ -128,7 +127,7 @@ const PathNamePair &GetProcBinary()
else
res.fname = wstr_to_utf8(fullpath);
TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str());
TRACE("Got binary: {}, {}", res.path, res.fname);
return res;
};
static const PathNamePair procbin{get_procbin()};
@@ -137,7 +136,7 @@ const PathNamePair &GetProcBinary()
namespace {
#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX)
#if !ALSOFT_UWP && !defined(_GAMING_XBOX)
struct CoTaskMemDeleter {
void operator()(void *mem) const { CoTaskMemFree(mem); }
};
@@ -145,26 +144,35 @@ struct CoTaskMemDeleter {
} // namespace
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
auto SearchDataFiles(const std::string_view ext) -> std::vector<std::string>
{
auto srchlock = std::lock_guard{gSearchLock};
/* Search the app-local directory. */
auto results = std::vector<std::string>{};
if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
DirectorySearch(*localpath, ext, &results);
else if(auto curpath = fs::current_path(); !curpath.empty())
DirectorySearch(curpath, ext, &results);
return results;
}
auto SearchDataFiles(const std::string_view ext, const std::string_view subdir)
-> std::vector<std::string>
{
std::lock_guard<std::mutex> srchlock{gSearchLock};
/* If the path is absolute, use it directly. */
std::vector<std::string> results;
auto path = std::filesystem::u8path(subdir);
auto path = fs::u8path(subdir);
if(path.is_absolute())
{
DirectorySearch(path, ext, &results);
return results;
}
/* Search the app-local directory. */
if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
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)
#if !ALSOFT_UWP && !defined(_GAMING_XBOX)
/* Search the local and global data dirs. */
for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData})
{
@@ -174,7 +182,7 @@ std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::
if(FAILED(hr) || !buffer || !*buffer)
continue;
DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results);
DirectorySearch(fs::path{buffer.get()}/path, ext, &results);
}
#endif
@@ -183,11 +191,11 @@ std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::
void SetRTPriority()
{
#if !defined(ALSOFT_UWP)
#if !ALSOFT_UWP
if(RTPrioLevel > 0)
{
if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
ERR("Failed to set priority level for thread\n");
ERR("Failed to set priority level for thread");
}
#endif
}
@@ -210,7 +218,7 @@ void SetRTPriority()
#include <pthread.h>
#include <sched.h>
#endif
#ifdef HAVE_RTKIT
#if HAVE_RTKIT
#include <sys/resource.h>
#include "dbus_wrap.h"
@@ -229,8 +237,8 @@ const PathNamePair &GetProcBinary()
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());
WARN("Failed to sysctl kern.proc.pathname: {}",
std::generic_category().message(errno));
else
{
auto procpath = std::vector<char>(pathlen+1, '\0');
@@ -244,8 +252,8 @@ const PathNamePair &GetProcBinary()
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());
ERR("proc_pidpath({}, ...) failed: {}", pid,
std::generic_category().message(errno));
else
pathname = procpath.data();
}
@@ -271,17 +279,16 @@ const PathNamePair &GetProcBinary()
for(const std::string_view name : SelfLinkNames)
{
try {
if(!std::filesystem::exists(name))
if(!fs::exists(name))
continue;
if(auto path = std::filesystem::read_symlink(name); !path.empty())
if(auto path = fs::read_symlink(name); !path.empty())
{
pathname = path.u8string();
pathname = al::u8_as_char(path.u8string());
break;
}
}
catch(std::exception& e) {
WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(),
e.what());
WARN("Exception getting symlink {}: {}", name, e.what());
}
}
}
@@ -296,36 +303,45 @@ const PathNamePair &GetProcBinary()
else
res.fname = pathname;
TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str());
TRACE("Got binary: \"{}\", \"{}\"", res.path, res.fname);
return res;
};
static const PathNamePair procbin{get_procbin()};
return procbin;
}
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir)
auto SearchDataFiles(const std::string_view ext) -> std::vector<std::string>
{
auto srchlock = std::lock_guard{gSearchLock};
/* Search the app-local directory. */
auto results = std::vector<std::string>{};
if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
DirectorySearch(*localpath, ext, &results);
else if(auto curpath = fs::current_path(); !curpath.empty())
DirectorySearch(curpath, ext, &results);
return results;
}
auto SearchDataFiles(const std::string_view ext, const std::string_view subdir)
-> std::vector<std::string>
{
std::lock_guard<std::mutex> srchlock{gSearchLock};
std::vector<std::string> results;
auto path = std::filesystem::u8path(subdir);
auto path = fs::u8path(subdir);
if(path.is_absolute())
{
DirectorySearch(path, ext, &results);
return results;
}
/* Search the app-local directory. */
if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
DirectorySearch(*localpath, ext, &results);
else if(auto curpath = std::filesystem::current_path(); !curpath.empty())
DirectorySearch(curpath, ext, &results);
/* Search local data dir */
if(auto datapath = al::getenv("XDG_DATA_HOME"))
DirectorySearch(std::filesystem::path{*datapath}/path, ext, &results);
DirectorySearch(fs::path{*datapath}/path, ext, &results);
else if(auto homepath = al::getenv("HOME"))
DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results);
DirectorySearch(fs::path{*homepath}/".local/share"/path, ext, &results);
/* Search global data dirs */
std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
@@ -341,12 +357,12 @@ std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::
curpos = nextpos;
if(!pathname.empty())
DirectorySearch(std::filesystem::path{pathname}/path, ext, &results);
DirectorySearch(fs::path{pathname}/path, ext, &results);
}
#ifdef ALSOFT_INSTALL_DATADIR
/* Search the installation data directory */
if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty())
if(auto instpath = fs::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty())
DirectorySearch(instpath/path, ext, &results);
#endif
@@ -376,24 +392,23 @@ bool SetRTPriorityPthread(int prio [[maybe_unused]])
err = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
if(err == 0) return true;
#endif
WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(),
err);
WARN("pthread_setschedparam failed: {} ({})", std::generic_category().message(err), err);
return false;
}
bool SetRTPriorityRTKit(int prio [[maybe_unused]])
{
#ifdef HAVE_RTKIT
#if HAVE_RTKIT
if(!HasDBus())
{
WARN("D-Bus not available\n");
WARN("D-Bus not available");
return false;
}
dbus::Error error;
dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())};
if(!conn)
{
WARN("D-Bus connection failed with %s: %s\n", error->name, error->message);
WARN("D-Bus connection failed with {}: {}", error->name, error->message);
return false;
}
@@ -405,11 +420,11 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]])
if(err == -ENOENT)
{
err = std::abs(err);
ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err);
ERR("Could not query RTKit: {} ({})", std::generic_category().message(err), err);
return false;
}
int rtmax{rtkit_get_max_realtime_priority(conn.get())};
TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin);
TRACE("Maximum real-time priority: {}, minimum niceness: {}", rtmax, nicemin);
auto limit_rttime = [](DBusConnection *c) -> int
{
@@ -422,8 +437,7 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]])
if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
return errno;
TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime,
static_cast<ulonglong>(rlim.rlim_max), static_cast<ulonglong>(rlim.rlim_cur));
TRACE("RTTime max: {} (hard: {}, soft: {})", umaxtime, rlim.rlim_max, rlim.rlim_cur);
if(rlim.rlim_max > umaxtime)
{
rlim.rlim_max = static_cast<rlim_t>(std::min<ulonglong>(umaxtime,
@@ -440,21 +454,21 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]])
{
err = limit_rttime(conn.get());
if(err != 0)
WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
std::generic_category().message(err).c_str(), err);
WARN("Failed to set RLIMIT_RTTIME for RTKit: {} ({})",
std::generic_category().message(err), err);
}
/* Limit the maximum real-time priority to half. */
rtmax = (rtmax+1)/2;
prio = std::clamp(prio, 1, rtmax);
TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax);
TRACE("Making real-time with priority {} (max: {})", 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::generic_category().message(err).c_str(), err);
WARN("Failed to set real-time priority: {} ({})",
std::generic_category().message(err), err);
}
/* Don't try to set the niceness for non-Linux systems. Standard POSIX has
* niceness as a per-process attribute, while the intent here is for the
@@ -464,19 +478,18 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]])
#ifdef __linux__
if(nicemin < 0)
{
TRACE("Making high priority with niceness %d\n", nicemin);
TRACE("Making high priority with niceness {}", nicemin);
err = rtkit_make_high_priority(conn.get(), 0, nicemin);
if(err == 0) return true;
err = std::abs(err);
WARN("Failed to set high priority: %s (%d)\n",
std::generic_category().message(err).c_str(), err);
WARN("Failed to set high priority: {} ({})", std::generic_category().message(err), err);
}
#endif /* __linux__ */
#else
WARN("D-Bus not supported\n");
WARN("D-Bus not supported");
#endif
return false;
}
+3 -1
View File
@@ -19,6 +19,8 @@ inline bool AllowRTTimeLimit{true};
void SetRTPriority();
std::vector<std::string> SearchDataFiles(const std::string_view ext, const std::string_view subdir);
auto SearchDataFiles(const std::string_view ext) -> std::vector<std::string>;
auto SearchDataFiles(const std::string_view ext, const std::string_view subdir)
-> std::vector<std::string>;
#endif /* CORE_HELPERS_H */
+155 -151
View File
@@ -12,7 +12,6 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <memory>
@@ -31,7 +30,9 @@
#include "alspan.h"
#include "alstring.h"
#include "ambidefs.h"
#include "filesystem.h"
#include "filters/splitter.h"
#include "fmt/core.h"
#include "helpers.h"
#include "logging.h"
#include "mixer/hrtfdefs.h"
@@ -103,11 +104,16 @@ constexpr uint MaxSampleRate{0xff'ff'ff};
static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large");
constexpr auto HeaderMarkerSize = 8_uz;
[[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; }
[[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; }
[[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; }
[[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; }
static_assert(GetMarker00Name().size() == HeaderMarkerSize);
static_assert(GetMarker01Name().size() == HeaderMarkerSize);
static_assert(GetMarker02Name().size() == HeaderMarkerSize);
static_assert(GetMarker03Name().size() == HeaderMarkerSize);
/* First value for pass-through coefficients (remaining are 0), used for omni-
* directional sounds. */
@@ -120,6 +126,11 @@ std::mutex EnumeratedHrtfLock;
std::vector<HrtfEntry> EnumeratedHrtfs;
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
* To access a memory buffer through the std::istream interface, a custom
* std::streambuf implementation is needed that has to do pointer manipulation
* for seeking. With C++23, we may be able to use std::spanstream instead.
*/
class databuf final : public std::streambuf {
int_type underflow() override
{ return traits_type::eof(); }
@@ -171,17 +182,18 @@ class databuf final : public std::streambuf {
}
public:
databuf(const al::span<char_type> data) noexcept
explicit databuf(const al::span<char_type> data) noexcept
{
setg(data.data(), data.data(), al::to_address(data.end()));
}
};
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
class idstream final : public std::istream {
databuf mStreamBuf;
public:
idstream(const al::span<char_type> data) : std::istream{nullptr}, mStreamBuf{data}
explicit idstream(const al::span<char_type> data) : std::istream{nullptr}, mStreamBuf{data}
{ init(&mStreamBuf); }
};
@@ -192,10 +204,9 @@ struct IdxBlend { uint idx; float blend; };
*/
IdxBlend CalcEvIndex(uint evcount, float ev)
{
ev = (al::numbers::pi_v<float>*0.5f + ev) * static_cast<float>(evcount-1) *
al::numbers::inv_pi_v<float>;
uint idx{float2uint(ev)};
ev = (al::numbers::inv_pi_v<float>*ev + 0.5f) * static_cast<float>(evcount-1);
const auto idx = float2uint(ev);
return IdxBlend{std::min(idx, evcount-1u), ev-static_cast<float>(idx)};
}
@@ -204,10 +215,9 @@ IdxBlend CalcEvIndex(uint evcount, float ev)
*/
IdxBlend CalcAzIndex(uint azcount, float az)
{
az = (al::numbers::pi_v<float>*2.0f + az) * static_cast<float>(azcount) *
(al::numbers::inv_pi_v<float>*0.5f);
uint idx{float2uint(az)};
az = (al::numbers::inv_pi_v<float>*0.5f*az + 1.0f) * static_cast<float>(azcount);
const auto idx = float2uint(az);
return IdxBlend{idx%azcount, az-static_cast<float>(idx)};
}
@@ -218,7 +228,7 @@ IdxBlend CalcAzIndex(uint azcount, float az)
* and azimuth in radians. The coefficients are normalized.
*/
void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread,
HrirArray &coeffs, const al::span<uint,2> delays) const
const HrirSpan coeffs, const al::span<uint,2> delays) const
{
const float dirfact{1.0f - (al::numbers::inv_pi_v<float>/2.0f * spread)};
@@ -269,17 +279,17 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float
delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne});
/* Calculate the blended HRIR coefficients. */
float *coeffout{al::assume_aligned<16>(coeffs[0].data())};
coeffout[0] = PassthruCoeff * (1.0f-dirfact);
coeffout[1] = PassthruCoeff * (1.0f-dirfact);
std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f);
auto coeffout = coeffs.begin();
coeffout[0][0] = PassthruCoeff * (1.0f-dirfact);
coeffout[0][1] = PassthruCoeff * (1.0f-dirfact);
std::fill_n(coeffout+1, size_t{HrirLength-1}, std::array{0.0f, 0.0f});
for(size_t c{0};c < 4;c++)
{
const float *srccoeffs{al::assume_aligned<16>(mCoeffs[idx[c]][0].data())};
const float mult{blend[c]};
auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float
{ return src*mult + coeff; };
std::transform(srccoeffs, srccoeffs + HrirLength*2_uz, coeffout, coeffout, blend_coeffs);
auto blend_coeffs = [mult](const float2 &src, const float2 &coeff) noexcept -> float2
{ return float2{{src[0]*mult + coeff[0], src[1]*mult + coeff[1]}}; };
std::transform(mCoeffs[idx[c]].cbegin(), mCoeffs[idx[c]].cend(), coeffout, coeffout,
blend_coeffs);
}
}
@@ -342,7 +352,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool
auto hrir_delay_round = [](const uint d) noexcept -> uint
{ return (d+HrirDelayFracHalf) >> HrirDelayFracBits; };
TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n",
TRACE("Min delay: {:.2f}, max delay: {:.2f}, FIR length: {}",
min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize);
auto tmpres = std::vector<std::array<double2,HrirLength>>(mChannels.size());
@@ -383,7 +393,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool
tmpres.clear();
const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)};
TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne},
TRACE("New max delay: {:.2f}, FIR length: {}", max_delay/double{HrirDelayFracOne},
max_length);
mIrSize = max_length;
}
@@ -420,35 +430,38 @@ std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, uint8_t irSize,
Hrtf->mIrSize = irSize;
/* Set up pointers to storage following the main HRTF struct. */
char *base = reinterpret_cast<char*>(Hrtf.get());
size_t offset{sizeof(HrtfStore)};
auto storage = al::span{reinterpret_cast<char*>(Hrtf.get()), total};
auto base = storage.begin();
ptrdiff_t offset{sizeof(HrtfStore)};
offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */
auto field_ = reinterpret_cast<HrtfStore::Field*>(base + offset);
offset += sizeof(field_[0])*fields.size();
auto field_ = al::span{reinterpret_cast<HrtfStore::Field*>(al::to_address(base + offset)),
fields.size()};
offset += ptrdiff_t(sizeof(field_[0])*fields.size());
offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */
auto elev_ = reinterpret_cast<HrtfStore::Elevation*>(base + offset);
offset += sizeof(elev_[0])*elevs.size();
auto elev_ = al::span{reinterpret_cast<HrtfStore::Elevation*>(al::to_address(base + offset)),
elevs.size()};
offset += ptrdiff_t(sizeof(elev_[0])*elevs.size());
offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */
auto coeffs_ = reinterpret_cast<HrirArray*>(base + offset);
offset += sizeof(coeffs_[0])*irCount;
auto coeffs_ = al::span{reinterpret_cast<HrirArray*>(al::to_address(base + offset)), irCount};
offset += ptrdiff_t(sizeof(coeffs_[0])*irCount);
auto delays_ = reinterpret_cast<ubyte2*>(base + offset);
offset += sizeof(delays_[0])*irCount;
auto delays_ = al::span{reinterpret_cast<ubyte2*>(al::to_address(base + offset)), irCount};
offset += ptrdiff_t(sizeof(delays_[0])*irCount);
if(offset != total)
if(size_t(offset) != total)
throw std::runtime_error{"HrtfStore allocation size mismatch"};
/* Copy input data to storage. */
std::uninitialized_copy(fields.cbegin(), fields.cend(), field_);
std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_);
std::uninitialized_copy_n(coeffs, irCount, coeffs_);
std::uninitialized_copy_n(delays, irCount, delays_);
std::uninitialized_copy(fields.cbegin(), fields.cend(), field_.begin());
std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_.begin());
std::uninitialized_copy_n(coeffs, irCount, coeffs_.begin());
std::uninitialized_copy_n(delays, irCount, delays_.begin());
/* Finally, assign the storage pointers. */
Hrtf->mFields = {field_, fields.size()};
Hrtf->mFields = field_;
Hrtf->mElev = elev_;
Hrtf->mCoeffs = coeffs_;
Hrtf->mDelays = delays_;
@@ -456,8 +469,8 @@ std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, uint8_t irSize,
return Hrtf;
}
void MirrorLeftHrirs(const al::span<const HrtfStore::Elevation> elevs, HrirArray *coeffs,
ubyte2 *delays)
void MirrorLeftHrirs(const al::span<const HrtfStore::Elevation> elevs, al::span<HrirArray> coeffs,
al::span<ubyte2> delays)
{
for(const auto &elev : elevs)
{
@@ -535,13 +548,13 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data)
if(irSize < MinIrLength || irSize > HrirLength)
{
ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength);
ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength);
return nullptr;
}
if(evCount < MinEvCount || evCount > MaxEvCount)
{
ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
evCount, MinEvCount, MaxEvCount);
ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount,
MaxEvCount);
return nullptr;
}
@@ -555,15 +568,15 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data)
{
if(elevs[i].irOffset <= elevs[i-1].irOffset)
{
ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", i, elevs[i].irOffset,
ERR("Invalid evOffset: evOffset[{}]={} (last={})", i, elevs[i].irOffset,
elevs[i-1].irOffset);
return nullptr;
}
}
if(irCount <= elevs.back().irOffset)
{
ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n",
elevs.size()-1, elevs.back().irOffset, irCount);
ERR("Invalid evOffset: evOffset[{}]={} (irCount={})", elevs.size()-1,
elevs.back().irOffset, irCount);
return nullptr;
}
@@ -572,16 +585,16 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data)
elevs[i-1].azCount = static_cast<ushort>(elevs[i].irOffset - elevs[i-1].irOffset);
if(elevs[i-1].azCount < MinAzCount || elevs[i-1].azCount > MaxAzCount)
{
ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n",
i-1, elevs[i-1].azCount, MinAzCount, MaxAzCount);
ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i-1, elevs[i-1].azCount,
MinAzCount, MaxAzCount);
return nullptr;
}
}
elevs.back().azCount = static_cast<ushort>(irCount - elevs.back().irOffset);
if(elevs.back().azCount < MinAzCount || elevs.back().azCount > MaxAzCount)
{
ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n",
elevs.size()-1, elevs.back().azCount, MinAzCount, MaxAzCount);
ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", elevs.size()-1,
elevs.back().azCount, MinAzCount, MaxAzCount);
return nullptr;
}
@@ -589,7 +602,7 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data)
auto delays = std::vector<ubyte2>(irCount);
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
val[0] = float(readle<int16_t>(data)) / 32768.0f;
}
for(auto &val : delays)
@@ -601,14 +614,14 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay)
{
ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay);
ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay);
return nullptr;
}
delays[i][0] <<= HrirDelayFracBits;
}
/* Mirror the left ear responses to the right ear. */
MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data());
MirrorLeftHrirs(elevs, coeffs, delays);
const std::array field{HrtfStore::Field{0.0f, evCount}};
return CreateHrtfStore(rate, static_cast<uint8_t>(irSize), field, elevs, coeffs.data(),
@@ -625,13 +638,13 @@ std::unique_ptr<HrtfStore> LoadHrtf01(std::istream &data)
if(irSize < MinIrLength || irSize > HrirLength)
{
ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength);
ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength);
return nullptr;
}
if(evCount < MinEvCount || evCount > MaxEvCount)
{
ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
evCount, MinEvCount, MaxEvCount);
ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount,
MaxEvCount);
return nullptr;
}
@@ -645,7 +658,7 @@ std::unique_ptr<HrtfStore> LoadHrtf01(std::istream &data)
{
if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount)
{
ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, elevs[i].azCount,
ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i, elevs[i].azCount,
MinAzCount, MaxAzCount);
return nullptr;
}
@@ -660,7 +673,7 @@ std::unique_ptr<HrtfStore> LoadHrtf01(std::istream &data)
auto delays = std::vector<ubyte2>(irCount);
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
val[0] = float(readle<int16_t>(data)) / 32768.0f;
}
for(auto &val : delays)
@@ -672,14 +685,14 @@ std::unique_ptr<HrtfStore> LoadHrtf01(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay)
{
ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay);
ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay);
return nullptr;
}
delays[i][0] <<= HrirDelayFracBits;
}
/* Mirror the left ear responses to the right ear. */
MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data());
MirrorLeftHrirs(elevs, coeffs, delays);
const std::array field{HrtfStore::Field{0.0f, evCount}};
return CreateHrtfStore(rate, irSize, field, elevs, coeffs.data(), delays.data());
@@ -702,23 +715,23 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
if(sampleType > SampleType_S24)
{
ERR("Unsupported sample type: %d\n", sampleType);
ERR("Unsupported sample type: {}", sampleType);
return nullptr;
}
if(channelType > ChanType_LeftRight)
{
ERR("Unsupported channel type: %d\n", channelType);
ERR("Unsupported channel type: {}", channelType);
return nullptr;
}
if(irSize < MinIrLength || irSize > HrirLength)
{
ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength);
ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength);
return nullptr;
}
if(fdCount < 1 || fdCount > MaxFdCount)
{
ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount,
ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount,
MaxFdCount);
return nullptr;
}
@@ -734,13 +747,13 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
if(distance < MinFdDistance || distance > MaxFdDistance)
{
ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance,
ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance,
MinFdDistance, MaxFdDistance);
return nullptr;
}
if(evCount < MinEvCount || evCount > MaxEvCount)
{
ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount,
ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount,
MinEvCount, MaxEvCount);
return nullptr;
}
@@ -749,14 +762,14 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
fields[f].evCount = evCount;
if(f > 0 && fields[f].distance <= fields[f-1].distance)
{
ERR("Field distance[%zu] is not after previous (%f > %f)\n", f, fields[f].distance,
ERR("Field distance[{}] is not after previous ({:f} > {:f})", f, fields[f].distance,
fields[f-1].distance);
return nullptr;
}
const size_t ebase{elevs.size()};
elevs.resize(ebase + evCount);
for(auto &elev : al::span<HrtfStore::Elevation>(elevs.data()+ebase, evCount))
for(auto &elev : al::span{elevs}.subspan(ebase, evCount))
elev.azCount = readle<uint8_t>(data);
if(!data || data.eof())
throw std::runtime_error{"Premature end of file"};
@@ -765,7 +778,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount)
{
ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e,
ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e,
elevs[ebase+e].azCount, MinAzCount, MaxAzCount);
return nullptr;
}
@@ -790,7 +803,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
val[0] = float(readle<int16_t>(data)) / 32768.0f;
}
}
@@ -798,7 +811,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
val[0] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
}
}
@@ -811,14 +824,14 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay)
{
ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay);
ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay);
return nullptr;
}
delays[i][0] <<= HrirDelayFracBits;
}
/* Mirror the left ear responses to the right ear. */
MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data());
MirrorLeftHrirs(elevs, coeffs, delays);
}
else if(channelType == ChanType_LeftRight)
{
@@ -826,7 +839,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
{
val[0] = float(readle<int16_t>(data)) / 32768.0f;
val[1] = float(readle<int16_t>(data)) / 32768.0f;
@@ -837,7 +850,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
{
val[0] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
val[1] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
@@ -856,12 +869,12 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay)
{
ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay);
ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay);
return nullptr;
}
if(delays[i][1] > MaxHrirDelay)
{
ERR("Invalid delays[%zu][1]: %d (%d)\n", i, delays[i][1], MaxHrirDelay);
ERR("Invalid delays[{}][1]: {} ({})", i, delays[i][1], MaxHrirDelay);
return nullptr;
}
delays[i][0] <<= HrirDelayFracBits;
@@ -954,18 +967,18 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
if(channelType > ChanType_LeftRight)
{
ERR("Unsupported channel type: %d\n", channelType);
ERR("Unsupported channel type: {}", channelType);
return nullptr;
}
if(irSize < MinIrLength || irSize > HrirLength)
{
ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength);
ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength);
return nullptr;
}
if(fdCount < 1 || fdCount > MaxFdCount)
{
ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount,
ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount,
MaxFdCount);
return nullptr;
}
@@ -981,13 +994,13 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
if(distance < MinFdDistance || distance > MaxFdDistance)
{
ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance,
ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance,
MinFdDistance, MaxFdDistance);
return nullptr;
}
if(evCount < MinEvCount || evCount > MaxEvCount)
{
ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount,
ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount,
MinEvCount, MaxEvCount);
return nullptr;
}
@@ -996,14 +1009,14 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
fields[f].evCount = evCount;
if(f > 0 && fields[f].distance > fields[f-1].distance)
{
ERR("Field distance[%zu] is not before previous (%f <= %f)\n", f, fields[f].distance,
fields[f-1].distance);
ERR("Field distance[{}] is not before previous ({:f} <= {:f})", f,
fields[f].distance, fields[f-1].distance);
return nullptr;
}
const size_t ebase{elevs.size()};
elevs.resize(ebase + evCount);
for(auto &elev : al::span<HrtfStore::Elevation>(elevs.data()+ebase, evCount))
for(auto &elev : al::span{elevs}.subspan(ebase, evCount))
elev.azCount = readle<uint8_t>(data);
if(!data || data.eof())
throw std::runtime_error{"Premature end of file"};
@@ -1012,7 +1025,7 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
{
if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount)
{
ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e,
ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e,
elevs[ebase+e].azCount, MinAzCount, MaxAzCount);
return nullptr;
}
@@ -1035,7 +1048,7 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
val[0] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
}
for(auto &val : delays)
@@ -1047,20 +1060,20 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay<<HrirDelayFracBits)
{
ERR("Invalid delays[%zu][0]: %f (%d)\n", i,
delays[i][0] / float{HrirDelayFracOne}, MaxHrirDelay);
ERR("Invalid delays[{}][0]: {:f} ({})", i, delays[i][0]/float{HrirDelayFracOne},
MaxHrirDelay);
return nullptr;
}
}
/* Mirror the left ear responses to the right ear. */
MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data());
MirrorLeftHrirs(elevs, coeffs, delays);
}
else if(channelType == ChanType_LeftRight)
{
for(auto &hrir : coeffs)
{
for(auto &val : al::span<float2>{hrir.data(), irSize})
for(auto &val : al::span{hrir}.first(irSize))
{
val[0] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
val[1] = static_cast<float>(readle<int,24>(data)) / 8388608.0f;
@@ -1078,14 +1091,14 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data)
{
if(delays[i][0] > MaxHrirDelay<<HrirDelayFracBits)
{
ERR("Invalid delays[%zu][0]: %f (%d)\n", i,
delays[i][0] / float{HrirDelayFracOne}, MaxHrirDelay);
ERR("Invalid delays[{}][0]: {:f} ({})", i, delays[i][0]/float{HrirDelayFracOne},
MaxHrirDelay);
return nullptr;
}
if(delays[i][1] > MaxHrirDelay<<HrirDelayFracBits)
{
ERR("Invalid delays[%zu][1]: %f (%d)\n", i,
delays[i][1] / float{HrirDelayFracOne}, MaxHrirDelay);
ERR("Invalid delays[{}][1]: {:f} ({})", i, delays[i][1]/float{HrirDelayFracOne},
MaxHrirDelay);
return nullptr;
}
}
@@ -1106,35 +1119,29 @@ void AddFileEntry(const std::string_view filename)
{
/* Check if this file has already been enumerated. */
auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(),
[filename](const HrtfEntry &entry) -> bool
{ return entry.mFilename == filename; });
[filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; });
if(enum_iter != EnumeratedHrtfs.cend())
{
TRACE("Skipping duplicate file entry %.*s\n", al::sizei(filename), filename.data());
TRACE("Skipping duplicate file entry {}", filename);
return;
}
/* TODO: Get a human-readable name from the HRTF data (possibly coming in a
* format update). */
size_t namepos{filename.rfind('/')+1};
if(!namepos) namepos = filename.rfind('\\')+1;
* format update).
*/
const auto namepos = std::max(filename.rfind('/')+1, filename.rfind('\\')+1);
const auto extpos = filename.substr(namepos).rfind('.');
size_t extpos{filename.rfind('.')};
if(extpos <= namepos) extpos = std::string::npos;
const auto basename = (extpos == std::string::npos) ?
filename.substr(namepos) : filename.substr(namepos, extpos);
const std::string_view basename{(extpos == std::string::npos) ?
filename.substr(namepos) : filename.substr(namepos, extpos-namepos)};
std::string newname{basename};
int count{1};
auto count = 1;
auto newname = std::string{basename};
while(checkName(newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(newname, filename);
newname = fmt::format("{} #{}", basename, ++count);
TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str());
const auto &entry = EnumeratedHrtfs.emplace_back(newname, filename);
TRACE("Adding file entry \"{}\"", entry.mFilename);
}
/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer
@@ -1142,32 +1149,26 @@ void AddFileEntry(const std::string_view filename)
*/
void AddBuiltInEntry(const std::string_view dispname, uint residx)
{
std::string filename{'!'+std::to_string(residx)+'_'};
filename += dispname;
auto filename = fmt::format("!{}_{}", residx, dispname);
auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(),
[&filename](const HrtfEntry &entry) -> bool
{ return entry.mFilename == filename; });
[&filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; });
if(enum_iter != EnumeratedHrtfs.cend())
{
TRACE("Skipping duplicate file entry %s\n", filename.c_str());
TRACE("Skipping duplicate file entry {}", filename);
return;
}
/* TODO: Get a human-readable name from the HRTF data (possibly coming in a
* format update). */
std::string newname{dispname};
int count{1};
auto count = 1;
auto newname = std::string{dispname};
while(checkName(newname))
{
newname = dispname;
newname += " #";
newname += std::to_string(++count);
}
const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename));
newname = fmt::format("{} #{}", dispname, ++count);
TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str());
const auto &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename));
TRACE("Adding built-in entry \"{}\"", entry.mFilename);
}
@@ -1201,6 +1202,9 @@ std::vector<std::string> EnumerateHrtf(std::optional<std::string> pathopt)
std::lock_guard<std::mutex> enumlock{EnumeratedHrtfLock};
EnumeratedHrtfs.clear();
for(const auto &fname : SearchDataFiles(".mhr"sv))
AddFileEntry(fname);
bool usedefaults{true};
if(pathopt)
{
@@ -1253,7 +1257,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate)
try {
if(devrate > MaxSampleRate)
{
WARN("Device sample rate too large for HRTF (%uhz > %uhz)\n", devrate, MaxSampleRate);
WARN("Device sample rate too large for HRTF ({}hz > {}hz)", devrate, MaxSampleRate);
return nullptr;
}
std::lock_guard<std::mutex> enumlock{EnumeratedHrtfLock};
@@ -1283,13 +1287,14 @@ try {
std::unique_ptr<std::istream> stream;
int residx{};
char ch{};
/* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */
if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_')
{
TRACE("Loading %s...\n", fname.c_str());
TRACE("Loading {}...", fname);
al::span<const char> res{GetResource(residx)};
if(res.empty())
{
ERR("Could not get resource %u, %.*s\n", residx, al::sizei(name), name.data());
ERR("Could not get resource {}, {}", residx, name);
return nullptr;
}
/* NOLINTNEXTLINE(*-const-cast) */
@@ -1297,44 +1302,44 @@ try {
}
else
{
TRACE("Loading %s...\n", fname.c_str());
auto fstr = std::make_unique<std::ifstream>(std::filesystem::u8path(fname),
TRACE("Loading {}...", fname);
auto fstr = std::make_unique<fs::ifstream>(fs::u8path(fname),
std::ios::binary);
if(!fstr->is_open())
{
ERR("Could not open %s\n", fname.c_str());
ERR("Could not open {}", fname);
return nullptr;
}
stream = std::move(fstr);
}
std::unique_ptr<HrtfStore> hrtf;
std::array<char,GetMarker03Name().size()> magic{};
auto hrtf = std::unique_ptr<HrtfStore>{};
auto magic = std::array<char,HeaderMarkerSize>{};
stream->read(magic.data(), magic.size());
if(stream->gcount() < static_cast<std::streamsize>(GetMarker03Name().size()))
ERR("%.*s data is too short (%zu bytes)\n", al::sizei(name),name.data(), stream->gcount());
if(stream->gcount() < std::streamsize{magic.size()})
ERR("{} data is too short ({} bytes)", name, stream->gcount());
else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()})
{
TRACE("Detected data set format v3\n");
TRACE("Detected data set format v3");
hrtf = LoadHrtf03(*stream);
}
else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()})
{
TRACE("Detected data set format v2\n");
TRACE("Detected data set format v2");
hrtf = LoadHrtf02(*stream);
}
else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()})
{
TRACE("Detected data set format v1\n");
TRACE("Detected data set format v1");
hrtf = LoadHrtf01(*stream);
}
else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()})
{
TRACE("Detected data set format v0\n");
TRACE("Detected data set format v0");
hrtf = LoadHrtf00(*stream);
}
else
ERR("Invalid header in %.*s: \"%.8s\"\n", al::sizei(name), name.data(), magic.data());
ERR("Invalid header in {}: \"{}\"", name, std::string_view{magic.data(), magic.size()});
stream.reset();
if(!hrtf)
@@ -1342,8 +1347,7 @@ try {
if(hrtf->mSampleRate != devrate)
{
TRACE("Resampling HRTF %.*s (%uhz -> %uhz)\n", al::sizei(name), name.data(),
hrtf->mSampleRate, devrate);
TRACE("Resampling HRTF {} ({}hz -> {}hz)", name, uint{hrtf->mSampleRate}, devrate);
/* Calculate the last elevation's index and get the total IR count. */
const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz,
@@ -1353,7 +1357,7 @@ try {
const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount};
/* Resample all the IRs. */
std::array<std::array<double,HrirLength>,2> inout;
std::array<std::array<double,HrirLength>,2> inout{};
PPhaseResampler rs;
rs.init(hrtf->mSampleRate, devrate);
for(size_t i{0};i < irCount;++i)
@@ -1393,7 +1397,7 @@ try {
float delay_scale{HrirDelayFracOne};
if(max_delay > MaxHrirDelay)
{
WARN("Resampled delay exceeds max (%.2f > %d)\n", max_delay, MaxHrirDelay);
WARN("Resampled delay exceeds max ({:.2f} > {})", max_delay, MaxHrirDelay);
delay_scale *= float{MaxHrirDelay} / max_delay;
}
@@ -1415,13 +1419,13 @@ try {
}
handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf));
TRACE("Loaded HRTF %.*s for sample rate %uhz, %u-sample filter\n", al::sizei(name),name.data(),
handle->mEntry->mSampleRate, handle->mEntry->mIrSize);
TRACE("Loaded HRTF {} for sample rate {}hz, {}-sample filter", name,
uint{handle->mEntry->mSampleRate}, uint{handle->mEntry->mIrSize});
return HrtfStorePtr{handle->mEntry.get()};
}
catch(std::exception& e) {
ERR("Failed to load %.*s: %s\n", al::sizei(name), name.data(), e.what());
ERR("Failed to load {}: {}", name, e.what());
return nullptr;
}
@@ -1429,13 +1433,13 @@ catch(std::exception& e) {
void HrtfStore::add_ref()
{
auto ref = IncrementRef(mRef);
TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
TRACE("HrtfStore {} increasing refcount to {}", decltype(std::declval<void*>()){this}, ref);
}
void HrtfStore::dec_ref()
{
auto ref = DecrementRef(mRef);
TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
TRACE("HrtfStore {} decreasing refcount to {}", decltype(std::declval<void*>()){this}, ref);
if(ref == 0)
{
std::lock_guard<std::mutex> loadlock{LoadedHrtfLock};
@@ -1446,7 +1450,7 @@ void HrtfStore::dec_ref()
HrtfStore *entry{hrtf.mEntry.get()};
if(entry && entry->mRef.load() == 0)
{
TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.c_str());
TRACE("Unloading unused HRTF {}", hrtf.mFilename);
hrtf.mEntry = nullptr;
return true;
}
+7 -8
View File
@@ -11,7 +11,6 @@
#include "almalloc.h"
#include "alspan.h"
#include "atomic.h"
#include "ambidefs.h"
#include "bufferline.h"
#include "flexarray.h"
@@ -20,7 +19,7 @@
struct alignas(16) HrtfStore {
std::atomic<uint> mRef;
std::atomic<uint> mRef{};
uint mSampleRate : 24;
uint mIrSize : 8;
@@ -38,12 +37,12 @@ struct alignas(16) 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) const;
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();
@@ -75,7 +74,7 @@ struct DirectHrtfState {
uint mIrSize{0};
al::FlexArray<HrtfChannelState> mChannels;
DirectHrtfState(size_t numchans) : mChannels{numchans} { }
explicit DirectHrtfState(size_t numchans) : mChannels{numchans} { }
/**
* Produces HRTF filter coefficients for decoding B-Format, given a set of
* virtual speaker positions, a matching decoding matrix, and per-order
+20 -59
View File
@@ -3,7 +3,6 @@
#include "logging.h"
#include <array>
#include <cctype>
#include <cstdarg>
#include <cstdio>
@@ -11,9 +10,10 @@
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include <string_view>
#include <utility>
#include "alspan.h"
#include "alstring.h"
#include "strutils.h"
@@ -35,6 +35,8 @@ LogLevel gLogLevel{LogLevel::Error};
namespace {
using namespace std::string_view_literals;
enum class LogState : uint8_t {
FirstRun,
Ready,
@@ -76,57 +78,23 @@ void al_set_log_callback(LogCallbackFunc callback, void *userptr)
}
}
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::span{"[ALSOFT] (--) "}.first<14>();
void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args)
{
const auto msg = fmt::vformat(fmt, std::move(args));
auto prefix = "[ALSOFT] (--) "sv;
switch(level)
{
case LogLevel::Disable: 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;
case LogLevel::Error: prefix = "[ALSOFT] (EE) "sv; break;
case LogLevel::Warning: prefix = "[ALSOFT] (WW) "sv; break;
case LogLevel::Trace: prefix = "[ALSOFT] (II) "sv; break;
}
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)
{
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()};
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);
fmt::println(logfile, "{}{}", prefix, msg);
fflush(logfile);
}
#if defined(_WIN32) && !defined(NDEBUG)
@@ -134,8 +102,7 @@ try {
* informational, warning, or error debug messages. So only print them for
* non-Release builds.
*/
std::wstring wstr{utf8_to_wstr(str)};
OutputDebugStringW(wstr.c_str());
OutputDebugStringW(utf8_to_wstr(fmt::format("{}{}\n", prefix, msg)).c_str());
#elif defined(__ANDROID__)
auto android_severity = [](LogLevel l) noexcept
{
@@ -150,26 +117,20 @@ try {
}
return ANDROID_LOG_ERROR;
};
__android_log_print(android_severity(level), "openal", "%s", str);
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */
__android_log_print(android_severity(level), "openal", "%.*s%s", al::sizei(prefix),
prefix.data(), msg.c_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(auto logcode = GetLevelCode(level))
{
if(gLogCallback)
gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast<int>(msg.size()));
gLogCallback(gLogCallbackPtr, *logcode, msg.data(), al::sizei(msg));
else if(gLogState == LogState::FirstRun)
gLogState = LogState::Disable;
}
}
}
catch(...) {
/* Swallow any exceptions */
}
+11 -9
View File
@@ -1,8 +1,9 @@
#ifndef CORE_LOGGING_H
#define CORE_LOGGING_H
#include <stdio.h>
#include <cstdio>
#include "fmt/core.h"
#include "opthelpers.h"
@@ -12,9 +13,9 @@ enum class LogLevel {
Warning,
Trace
};
extern LogLevel gLogLevel;
DECL_HIDDEN extern LogLevel gLogLevel;
extern FILE *gLogFile;
DECL_HIDDEN extern FILE *gLogFile;
using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept;
@@ -22,12 +23,13 @@ using LogCallbackFunc = void(*)(void *userptr, char level, const char *message,
void al_set_log_callback(LogCallbackFunc callback, void *userptr);
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
#else
[[gnu::format(printf,2,3)]]
#endif
void al_print(LogLevel level, const char *fmt, ...) noexcept;
void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args);
template<typename ...Args>
void al_print(LogLevel level, fmt::format_string<Args...> fmt, Args&& ...args) noexcept
try {
al_print_impl(level, fmt, fmt::make_format_args(args...));
} catch(...) { }
#define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__)
+62 -57
View File
@@ -11,7 +11,6 @@
#include <limits>
#include <new>
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "opthelpers.h"
@@ -20,7 +19,7 @@
/* These structures assume BufferLineSize is a power of 2. */
static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2");
struct SlidingHold {
struct SIMDALIGN SlidingHold {
alignas(16) FloatBufferLine mValues;
std::array<uint,BufferLineSize> mExpiries;
uint mLowerIndex;
@@ -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.
@@ -101,21 +102,25 @@ void ShiftSlidingHold(SlidingHold *Hold, const uint n)
/* Multichannel compression is linked via the absolute maximum of all
* channels.
*/
void Compressor::linkChannels(const uint SamplesToDo, const FloatBufferLine *OutBuffer)
void Compressor::linkChannels(const uint SamplesToDo,
const al::span<const FloatBufferLine> OutBuffer)
{
ASSUME(SamplesToDo > 0);
ASSUME(SamplesToDo <= BufferLineSize);
const auto side_begin = mSideChain.begin() + 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())};
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(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs);
std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(),
max_abs);
};
std::for_each(OutBuffer, OutBuffer+mNumChans, fill_max);
for(const FloatBufferLine &input : OutBuffer)
fill_max(input);
}
/* This calculates the squared crest factor of the control signal for the
@@ -130,6 +135,7 @@ void Compressor::crestDetector(const uint SamplesToDo)
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
{
@@ -139,8 +145,8 @@ void Compressor::crestDetector(const uint SamplesToDo)
y2_rms = lerpf(x2, y2_rms, a_crest);
return y2_peak / y2_rms;
};
const auto side_begin = mSideChain.begin() + mLookAhead;
std::transform(side_begin, side_begin+SamplesToDo, mCrestFactor.begin(), calc_crest);
const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest);
mLastPeakSq = y2_peak;
mLastRmsSq = y2_rms;
@@ -153,10 +159,11 @@ void Compressor::crestDetector(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 logarithmic. */
const auto side_begin = mSideChain.begin() + mLookAhead;
std::transform(side_begin, side_begin+SamplesToDo, side_begin,
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)); });
}
@@ -167,6 +174,7 @@ void Compressor::peakDetector(const uint SamplesToDo)
void Compressor::peakHoldDetector(const uint SamplesToDo)
{
ASSUME(SamplesToDo > 0);
ASSUME(SamplesToDo <= BufferLineSize);
SlidingHold *hold{mHold.get()};
uint i{0};
@@ -175,8 +183,8 @@ void Compressor::peakHoldDetector(const uint SamplesToDo)
const float x_G{std::log(std::max(0.000001f, x_abs))};
return UpdateSlidingHold(hold, i++, x_G);
};
auto side_begin = mSideChain.begin() + 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);
}
@@ -284,40 +292,40 @@ void Compressor::gainCompressor(const uint SamplesToDo)
* reaching the offending impulse. This is best used when operating as a
* limiter.
*/
void Compressor::signalDelay(const uint SamplesToDo, FloatBufferLine *OutBuffer)
void Compressor::signalDelay(const uint SamplesToDo, const al::span<FloatBufferLine> OutBuffer)
{
const size_t numChans{mNumChans};
const uint lookAhead{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>(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());
}
}
}
std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const float SampleRate,
const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain,
const bool AutoDeclip, const float LookAheadTime, const float HoldTime, const float PreGainDb,
const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb,
const float AttackTime, const float ReleaseTime)
const FlagBits autoflags, const float LookAheadTime, const float HoldTime,
const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio,
const float KneeDb, const float AttackTime, const float ReleaseTime)
{
const auto lookAhead = static_cast<uint>(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f,
BufferLineSize-1.0f));
@@ -325,12 +333,11 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
BufferLineSize-1.0f));
auto Comp = CompressorPtr{new Compressor{}};
Comp->mNumChans = NumChans;
Comp->mAuto.Knee = AutoKnee;
Comp->mAuto.Attack = AutoAttack;
Comp->mAuto.Release = AutoRelease;
Comp->mAuto.PostGain = AutoPostGain;
Comp->mAuto.Declip = AutoPostGain && AutoDeclip;
Comp->mAuto.Knee = autoflags.test(AutoKnee);
Comp->mAuto.Attack = autoflags.test(AutoAttack);
Comp->mAuto.Release = autoflags.test(AutoRelease);
Comp->mAuto.PostGain = autoflags.test(AutoPostGain);
Comp->mAuto.Declip = autoflags.test(AutoPostGain) && autoflags.test(AutoDeclip);
Comp->mLookAhead = lookAhead;
Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f);
Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb;
@@ -373,26 +380,24 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
Compressor::~Compressor() = default;
void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
void Compressor::process(const uint SamplesToDo, const al::span<FloatBufferLine> InOut)
{
const size_t numChans{mNumChans};
ASSUME(SamplesToDo > 0);
ASSUME(numChans > 0);
ASSUME(SamplesToDo <= BufferLineSize);
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,
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(InOut.begin(), InOut.end(), apply_gain);
}
linkChannels(SamplesToDo, OutBuffer);
linkChannels(SamplesToDo, InOut);
if(mAuto.Attack || mAuto.Release)
crestDetector(SamplesToDo);
@@ -405,18 +410,18 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
gainCompressor(SamplesToDo);
if(!mDelay.empty())
signalDelay(SamplesToDo, OutBuffer);
signalDelay(SamplesToDo, InOut);
const auto sideChain = al::span{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 inout) noexcept -> void
{
float *buffer{al::assume_aligned<16>(input.data())};
const float *gains{al::assume_aligned<16>(sideChain.data())};
std::transform(gains, gains+SamplesToDo, buffer, buffer,
[](const float g, const float s) noexcept { return g * s; });
const auto buffer = assume_aligned_span<16>(inout);
std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(),
std::multiplies{});
};
std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
for(const FloatBufferSpan inout : InOut)
apply_comp(inout);
auto side_begin = mSideChain.begin() + SamplesToDo;
std::copy(side_begin, side_begin+mLookAhead, mSideChain.begin());
const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead);
std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin());
}
+14 -12
View File
@@ -2,12 +2,13 @@
#define CORE_MASTERING_H
#include <array>
#include <bitset>
#include <memory>
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "bufferline.h"
#include "opthelpers.h"
#include "vector.h"
struct SlidingHold;
@@ -25,9 +26,7 @@ using uint = unsigned int;
*
* http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
*/
class Compressor {
size_t mNumChans{0u};
class SIMDALIGN Compressor {
struct AutoFlags {
bool Knee : 1;
bool Attack : 1;
@@ -67,16 +66,21 @@ class Compressor {
Compressor() = default;
void linkChannels(const uint SamplesToDo, const FloatBufferLine *OutBuffer);
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, FloatBufferLine *OutBuffer);
void signalDelay(const uint SamplesToDo, const al::span<FloatBufferLine> OutBuffer);
public:
enum {
AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, FlagsCount
};
using FlagBits = std::bitset<FlagsCount>;
~Compressor();
void process(const uint SamplesToDo, FloatBufferLine *OutBuffer);
void process(const uint SamplesToDo, al::span<FloatBufferLine> InOut);
[[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; }
/**
@@ -106,11 +110,9 @@ public:
* automating release time.
*/
static std::unique_ptr<Compressor> Create(const size_t NumChans, const float SampleRate,
const bool AutoKnee, const bool AutoAttack, const bool AutoRelease,
const bool AutoPostGain, const bool AutoDeclip, const float LookAheadTime,
const float HoldTime, const float PreGainDb, const float PostGainDb,
const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime,
const float ReleaseTime);
const FlagBits autoflags, const float LookAheadTime, const float HoldTime,
const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio,
const float KneeDb, const float AttackTime, const float ReleaseTime);
};
using CompressorPtr = std::unique_ptr<Compressor>;
+5 -3
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"
@@ -85,9 +87,9 @@ std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, c
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; });
std::fill(iter, gains.end(), 0.0f);
+12 -13
View File
@@ -3,34 +3,33 @@
#include <array>
#include <cmath>
#include <stddef.h>
#include <type_traits>
#include <cstddef>
#include "alspan.h"
#include "ambidefs.h"
#include "bufferline.h"
#include "devformat.h"
#include "opthelpers.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;
DECL_HIDDEN 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)
DECL_HIDDEN extern MixerOneFunc MixSamplesOne;
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); }
+26 -21
View File
@@ -30,13 +30,16 @@ inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
enum class Resampler : std::uint8_t {
Point,
Linear,
Cubic,
Spline,
Gaussian,
FastBSinc12,
BSinc12,
FastBSinc24,
BSinc24,
FastBSinc48,
BSinc48,
Max = BSinc24
Max = BSinc48
};
/* Interpolator state. Kind of a misnomer since the interpolator itself is
@@ -51,7 +54,7 @@ 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 {
@@ -59,49 +62,51 @@ struct CubicState {
* each subsequent phase index follows contiguously.
*/
al::span<const CubicCoefficients,CubicPhaseCount> filter;
CubicState(al::span<const CubicCoefficients,CubicPhaseCount> f) : filter{f} { }
explicit CubicState(al::span<const CubicCoefficients,CubicPhaseCount> f) : filter{f} { }
};
using InterpState = std::variant<std::monostate,CubicState,BsincState>;
using ResamplerFunc = void(*)(const InterpState *state, const float *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 *src, uint frac, const uint increment,
const al::span<float> dst);
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,
const al::span<float,BufferLineSize> 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>
constexpr void InitPosArrays(uint frac, const uint increment, const al::span<uint,N> frac_arr,
const al::span<uint,N> pos_arr)
constexpr void InitPosArrays(uint pos, uint frac, const uint increment,
const al::span<uint,N> frac_arr, const al::span<uint,N> pos_arr)
{
static_assert(pos_arr.size() == frac_arr.size());
pos_arr[0] = 0;
pos_arr[0] = pos;
frac_arr[0] = frac;
for(size_t i{1};i < pos_arr.size();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);
+45 -35
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,46 +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,
const al::span<float,BufferLineSize> 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.data())};
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 */
+153 -120
View File
@@ -12,6 +12,7 @@
#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"
@@ -34,214 +35,246 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
constexpr
auto do_point(const float *vals, const uint) noexcept -> float { return vals[0]; }
constexpr
auto do_lerp(const float *vals, const uint frac) noexcept -> float
{ return lerpf(vals[0], vals[1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
constexpr
auto do_cubic(const CubicState &istate, const float *vals, const uint frac) noexcept -> float
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 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];
}
constexpr
auto do_bsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float
[[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.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 *fil{istate.filter + m*pi*2_uz};
const float *phd{fil + m};
const float *scd{fil + BSincPhaseCount*2_uz*m};
const float *spd{scd + m};
/* Apply the scale and phase interpolated filter. */
float r{0.0f};
for(size_t j_f{0};j_f < m;j_f++)
r += (fil[j_f] + istate.sf*scd[j_f] + pf*(phd[j_f] + istate.sf*spd[j_f])) * vals[j_f];
return r;
}
constexpr
auto do_fastbsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float
{
const size_t m{istate.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 *fil{istate.filter + m*pi*2_uz};
const float *phd{fil + 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[j_f];
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] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+j_f];
return r;
}
template<float(&Sampler)(const float*, const uint)noexcept>
void DoResample(const float *src, uint frac, const uint increment, const al::span<float> dst)
{
ASSUME(frac < MixerFracOne);
std::generate(dst.begin(), dst.end(), [&src,&frac,increment]() -> float
{
const float output{Sampler(src, frac)};
frac += increment;
src += frac>>MixerFracBits;
frac &= MixerFracMask;
return output;
});
}
template<typename U, float(&Sampler)(const U&, const float*,const uint)noexcept>
void DoResample(const U istate, const float *src, uint frac, const uint increment,
template<SamplerNST Sampler>
void DoResample(const al::span<const float> src, uint frac, const uint increment,
const al::span<float> dst)
{
ASSUME(frac < MixerFracOne);
std::generate(dst.begin(), dst.end(), [istate,&src,&frac,increment]() -> float
size_t pos{0};
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float
{
const float output{Sampler(istate, src, frac)};
const float output{Sampler(src, pos, frac)};
frac += increment;
src += frac>>MixerFracBits;
pos += frac>>MixerFracBits;
frac &= MixerFracMask;
return output;
});
}
constexpr void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize,
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*, const float *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>(src, frac, increment, dst); }
{ DoResample<do_point>(src.subspan(MaxResamplerEdge), frac, increment, dst); }
template<>
void Resample_<LerpTag,CTag>(const InterpState*, const float *src, uint frac, const uint increment,
const al::span<float> dst)
{ DoResample<do_lerp>(src, frac, increment, dst); }
template<>
void Resample_<CubicTag,CTag>(const InterpState *state, const float *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<CubicState,do_cubic>(std::get<CubicState>(*state), src-1, frac, increment, dst); }
{ DoResample<do_lerp>(src.subspan(MaxResamplerEdge), frac, increment, dst); }
template<>
void Resample_<BSincTag,CTag>(const InterpState *state, const float *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)
{
const auto istate = std::get<BsincState>(*state);
DoResample<BsincState,do_bsinc>(istate, src-istate.l, frac, increment, dst);
DoResample<CubicState,do_cubic>(std::get<CubicState>(*state), src.subspan(MaxResamplerEdge-1),
frac, increment, dst);
}
template<>
void Resample_<FastBSincTag,CTag>(const InterpState *state, const float *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)
{
const auto istate = std::get<BsincState>(*state);
DoResample<BsincState,do_fastbsinc>(istate, src-istate.l, frac, increment, dst);
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,
const al::span<float,BufferLineSize> 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 = std::min(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 = std::min(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);
}
+183 -133
View File
@@ -14,10 +14,12 @@
#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 NEONTag;
struct LerpTag;
struct CubicTag;
@@ -63,60 +65,62 @@ 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)
{
auto dup_samples = [left,right]
ASSUME(IrSize >= MinIrLength);
ASSUME(IrSize <= HrirLength);
auto dup_samples = [left,right]() -> float32x4_t
{
float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)};
return vcombine_f32(leftright2, leftright2);
};
const float32x4_t leftright4{dup_samples()};
const auto leftright4 = dup_samples();
ASSUME(IrSize >= MinIrLength);
/* Using a loop here instead of std::transform since some builds seem to
* have an issue with accessing an array/span of float32x4_t.
*/
for(size_t c{0};c < IrSize;c += 2)
{
float32x4_t vals = vld1q_f32(&Values[c][0]);
float32x4_t coefs = vld1q_f32(&Coeffs[c][0]);
vals = vmlaq_f32(vals, coefs, leftright4);
auto vals = vld1q_f32(&Values[c][0]);
vals = vmlaq_f32(vals, vld1q_f32(&Coeffs[c][0]), leftright4);
vst1q_f32(&Values[c][0], vals);
}
}
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
const size_t aligned_len, size_t Counter)
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.
@@ -124,43 +128,70 @@ 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 *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);
@@ -169,18 +200,19 @@ void Resample_<LerpTag,NEONTag>(const InterpState*, const float *src, uint frac,
const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne);
const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask);
alignas(16) std::array<uint,4> pos_, frac_;
InitPosArrays(frac, increment, al::span{frac_}, al::span{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 vecout = al::span<float32x4_t>{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
auto vecout = al::span{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t
{
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_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])};
@@ -197,24 +229,26 @@ void Resample_<LerpTag,NEONTag>(const InterpState*, const float *src, uint frac,
if(size_t todo{dst.size()&3})
{
src += vgetq_lane_u32(pos4, 0);
auto pos = size_t{vgetq_lane_u32(pos4, 0)};
frac = vgetq_lane_u32(frac4, 0);
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment]
const auto out = dst.last(todo);
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]
{
const float out{lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne))};
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;
return out;
return output;
});
}
}
template<>
void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *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);
@@ -225,23 +259,23 @@ void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *src, uin
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(frac, increment, al::span{frac_}, al::span{pos_});
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())};
src -= 1;
auto vecout = al::span<float32x4_t>{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t
auto vecout = al::span{reinterpret_cast<float32x4_t*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]
{
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)};
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)};
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])};
const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)};
const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount);
@@ -276,10 +310,11 @@ void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *src, uin
if(const size_t todo{dst.size()&3})
{
src += vgetq_lane_u32(pos4, 0);
auto pos = size_t{vgetq_lane_u32(pos4, 0)};
frac = vgetq_lane_u32(frac4, 0);
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter]
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)};
@@ -287,13 +322,13 @@ void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *src, uin
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))};
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;
src += frac>>MixerFracBits;
pos += frac>>MixerFracBits;
frac &= MixerFracMask;
return output;
});
@@ -301,31 +336,34 @@ void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *src, uin
}
template<>
void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *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 auto &bsinc = std::get<BsincState>(*state);
const float *const filter{bsinc.filter};
const float32x4_t sf4{vdupq_n_f32(bsinc.sf)};
const size_t m{bsinc.m};
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 -= bsinc.l;
std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float
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 *fil{filter + m*pi*2_uz};
const float *phd{fil + m};
const float *scd{fil + BSincPhaseCount*2_uz*m};
const float *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};
@@ -335,7 +373,7 @@ void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *src, uin
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);
}
@@ -343,35 +381,38 @@ void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *src, uin
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 *src,
void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const al::span<const float> src,
uint frac, const uint increment, const al::span<float> dst)
{
const auto &bsinc = std::get<BsincState>(*state);
const float *const filter{bsinc.filter};
const size_t m{bsinc.m};
const auto m = size_t{bsinc.m};
ASSUME(m > 0);
ASSUME(m <= MaxResamplerPadding);
ASSUME(frac < MixerFracOne);
src -= bsinc.l;
std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float
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 *fil{filter + m*pi*2_uz};
const float *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};
@@ -379,7 +420,7 @@ void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *src,
/* 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);
}
@@ -387,7 +428,7 @@ void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *src,
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;
});
@@ -395,50 +436,59 @@ void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *src,
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,
const al::span<float,BufferLineSize> 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 = std::min(Counter, InSamples.size());
const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len;
if((OutPos&3) != 0) UNLIKELY
return Mix_<CTag>(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos);
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
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 = std::min(Counter, InSamples.size());
const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len;
if((reinterpret_cast<uintptr_t>(OutBuffer.data())&15) != 0) UNLIKELY
return Mix_<CTag>(InSamples, OutBuffer, CurrentGain, TargetGain, Counter);
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
aligned_len, Counter);
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
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, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter);
}
+178 -128
View File
@@ -16,10 +16,12 @@
#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 SSETag;
struct CubicTag;
struct BSincTag;
@@ -43,40 +45,45 @@ 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)); }
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 = vmadd(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);
@@ -90,37 +97,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 = CurrentGain;
auto step_count = 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 = vmadd(dry4, val4, vmadd(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.
@@ -128,51 +136,78 @@ 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 *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 auto filter = std::get<CubicState>(*state).filter;
src -= 1;
std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter]() -> float
size_t pos{MaxResamplerEdge-1};
std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float
{
const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount);
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
@@ -184,47 +219,50 @@ void Resample_<CubicTag,SSETag>(const InterpState *state, const float *src, uint
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));
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 *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 auto &bsinc = std::get<BsincState>(*state);
const float *const filter{bsinc.filter};
const __m128 sf4{_mm_set1_ps(bsinc.sf)};
const size_t m{bsinc.m};
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 -= bsinc.l;
std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float
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 *fil{filter + m*pi*2_uz};
const float *phd{fil + m};
const float *scd{fil + BSincPhaseCount*2_uz*m};
const float *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)) */
@@ -232,61 +270,64 @@ void Resample_<BSincTag,SSETag>(const InterpState *state, const float *src, uint
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 = vmadd(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));
const float output{_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 *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 auto &bsinc = std::get<BsincState>(*state);
const float *const filter{bsinc.filter};
const size_t m{bsinc.m};
const auto m = size_t{bsinc.m};
ASSUME(m > 0);
ASSUME(m <= MaxResamplerPadding);
ASSUME(frac < MixerFracOne);
src -= bsinc.l;
std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float
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 *fil{filter + m*pi*2_uz};
const float *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 = vmadd(_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 = vmadd(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));
const float output{_mm_cvtss_f32(r4)};
const auto output = _mm_cvtss_f32(r4);
frac += increment;
src += frac>>MixerFracBits;
pos += frac>>MixerFracBits;
frac &= MixerFracMask;
return output;
});
@@ -294,50 +335,59 @@ void Resample_<FastBSincTag,SSETag>(const InterpState *state, const float *src,
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,
const al::span<float,BufferLineSize> 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 = std::min(Counter, InSamples.size());
const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len;
if((OutPos&3) != 0) UNLIKELY
return Mix_<CTag>(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos);
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
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 = std::min(Counter, InSamples.size());
const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len;
if((reinterpret_cast<uintptr_t>(OutBuffer.data())&15) != 0) UNLIKELY
return Mix_<CTag>(InSamples, OutBuffer, CurrentGain, TargetGain, Counter);
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
aligned_len, Counter);
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
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, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter);
}
+29 -24
View File
@@ -31,6 +31,7 @@
#include "alnumeric.h"
#include "alspan.h"
#include "core/cubic_defs.h"
#include "core/resampler_limits.h"
#include "defs.h"
#include "opthelpers.h"
@@ -57,7 +58,7 @@ force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexce
} // namespace
template<>
void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *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);
@@ -66,20 +67,21 @@ void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *src, uint frac,
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
alignas(16) std::array<uint,4> pos_, frac_;
InitPosArrays(frac, increment, al::span{frac_}, al::span{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 vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128
{
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])};
@@ -96,24 +98,26 @@ void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *src, uint frac,
if(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));
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment]()
const auto out = dst.last(todo);
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]()
{
const float out{lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne))};
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;
return out;
return smp;
});
}
}
template<>
void Resample_<CubicTag,SSE2Tag>(const InterpState *state, const float *src, uint frac,
const uint increment, const al::span<float> dst)
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);
@@ -124,25 +128,25 @@ void Resample_<CubicTag,SSE2Tag>(const InterpState *state, const float *src, uin
const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)};
const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)};
alignas(16) std::array<uint,4> pos_, frac_;
InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_});
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]))};
src -= 1;
auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128
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)));
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)};
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));
@@ -183,10 +187,11 @@ void Resample_<CubicTag,SSE2Tag>(const InterpState *state, const float *src, uin
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));
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter]
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)};
@@ -194,14 +199,14 @@ void Resample_<CubicTag,SSE2Tag>(const InterpState *state, const float *src, uin
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))};
__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;
return output;
});
+30 -25
View File
@@ -32,6 +32,7 @@
#include "alnumeric.h"
#include "alspan.h"
#include "core/cubic_defs.h"
#include "core/resampler_limits.h"
#include "defs.h"
#include "opthelpers.h"
@@ -58,7 +59,7 @@ force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexce
} // namespace
template<>
void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *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);
@@ -67,20 +68,21 @@ void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *src, uint frac,
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
alignas(16) std::array<uint,4> pos_, frac_;
InitPosArrays(frac, increment, al::span{frac_}, al::span{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 vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128
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 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])};
@@ -101,24 +103,26 @@ void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *src, uint frac,
* 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));
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment]
auto out = dst.last(todo);
std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]
{
const float out{lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne))};
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;
return out;
return smp;
});
}
}
template<>
void Resample_<CubicTag,SSE4Tag>(const InterpState *state, const float *src, uint frac,
const uint increment, const al::span<float> dst)
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);
@@ -129,25 +133,25 @@ void Resample_<CubicTag,SSE4Tag>(const InterpState *state, const float *src, uin
const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)};
const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)};
alignas(16) std::array<uint,4> pos_, frac_;
InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_});
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]))};
src -= 1;
auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4};
std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128
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));
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)};
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));
@@ -188,10 +192,11 @@ void Resample_<CubicTag,SSE4Tag>(const InterpState *state, const float *src, uin
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));
std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter]
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)};
@@ -199,14 +204,14 @@ void Resample_<CubicTag,SSE4Tag>(const InterpState *state, const float *src, uin
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))};
__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;
return output;
});
+1
View File
@@ -69,6 +69,7 @@ namespace {
inline pid_t _gettid()
{
#ifdef __linux__
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */
return static_cast<pid_t>(syscall(SYS_gettid));
#elif defined(__FreeBSD__)
long pid{};
+31 -27
View File
@@ -4,45 +4,49 @@
#include "storage_formats.h"
#include <cstdint>
#include <string_view>
namespace {
using namespace std::string_view_literals;
} // namespace
const char *NameFromFormat(FmtType type) noexcept
auto NameFromFormat(FmtType type) noexcept -> std::string_view
{
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";
case FmtUByte: return "UInt8"sv;
case FmtShort: return "Int16"sv;
case FmtInt: return "Int32"sv;
case FmtFloat: return "Float"sv;
case FmtDouble: return "Double"sv;
case FmtMulaw: return "muLaw"sv;
case FmtAlaw: return "aLaw"sv;
case FmtIMA4: return "IMA4 ADPCM"sv;
case FmtMSADPCM: return "MS ADPCM"sv;
}
return "<internal error>";
return "<internal error>"sv;
}
const char *NameFromFormat(FmtChannels channels) noexcept
auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view
{
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)";
case FmtMono: return "Mono"sv;
case FmtStereo: return "Stereo"sv;
case FmtRear: return "Rear"sv;
case FmtQuad: return "Quadraphonic"sv;
case FmtX51: return "Surround 5.1"sv;
case FmtX61: return "Surround 6.1"sv;
case FmtX71: return "Surround 7.1"sv;
case FmtBFormat2D: return "B-Format 2D"sv;
case FmtBFormat3D: return "B-Format 3D"sv;
case FmtUHJ2: return "UHJ2"sv;
case FmtUHJ3: return "UHJ3"sv;
case FmtUHJ4: return "UHJ4"sv;
case FmtSuperStereo: return "Super Stereo"sv;
case FmtMonoDup: return "Mono (dup)"sv;
}
return "<internal error>";
return "<internal error>"sv;
}
uint BytesFromFmt(FmtType type) noexcept
+4 -2
View File
@@ -1,6 +1,8 @@
#ifndef CORE_STORAGE_FORMATS_H
#define CORE_STORAGE_FORMATS_H
#include <string_view>
using uint = unsigned int;
/* Storable formats */
@@ -43,8 +45,8 @@ enum class AmbiScaling : unsigned char {
UHJ,
};
const char *NameFromFormat(FmtType type) noexcept;
const char *NameFromFormat(FmtChannels channels) noexcept;
auto NameFromFormat(FmtType type) noexcept -> std::string_view;
auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view;
uint BytesFromFmt(FmtType type) noexcept;
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept;
+229 -207
View File
@@ -4,23 +4,27 @@
#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"
#include "vector.h"
UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
namespace {
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
@@ -58,55 +62,48 @@ struct SegmentedFilter {
SegmentedFilter() : mFft{sFftLength, PFFFT_REAL}
{
using complex_d = std::complex<double>;
constexpr size_t fft_size{N};
constexpr size_t half_size{fft_size / 2};
static constexpr size_t fft_size{N};
/* To set up the filter, we need to generate the desired response.
* Start with a pure delay that passes all frequencies through.
/* To set up the filter, we first need to generate the desired
* response (not reversed).
*/
auto fftBuffer = std::vector<complex_d>(fft_size, complex_d{});
fftBuffer[half_size] = 1.0;
auto tmpBuffer = std::vector<double>(fft_size, 0.0);
for(std::size_t i{0};i < fft_size/2;++i)
{
const auto k = int{fft_size/2} - static_cast<int>(i*2 + 1);
/* Convert to the frequency domain, shift the phase of each bin by +90
* degrees, then convert back to the time domain.
*
* NOTE: The 0- and half-frequency are always real for a real signal.
* To maintain that and their phase (0 or pi), they're heavily
* attenuated instead of shifted like the others.
*/
forward_fft(al::span{fftBuffer});
fftBuffer[0] *= std::numeric_limits<double>::epsilon();
for(size_t i{1};i < half_size;++i)
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
fftBuffer[half_size] *= std::numeric_limits<double>::epsilon();
for(size_t i{half_size+1};i < fft_size;++i)
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
inverse_fft(al::span{fftBuffer});
const auto w = 2.0*al::numbers::pi/double{fft_size} * static_cast<double>(i*2 + 1);
const auto window = 0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w)
- 0.0106411*std::cos(3.0*w);
const auto 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).
*/
auto fftBuffer2 = std::vector<complex_d>(sFftLength);
using complex_d = std::complex<double>;
auto fftBuffer = std::vector<complex_d>(sFftLength);
auto fftTmp = al::vector<float,16>(sFftLength);
float *filter{mFilterData.data()};
auto filter = mFilterData.begin();
for(size_t s{0};s < sNumSegments;++s)
{
for(size_t i{0};i < sSampleLength;++i)
fftBuffer2[i] = fftBuffer[sSampleLength*s + i].real() / double{fft_size};
std::fill_n(fftBuffer2.data()+sSampleLength, sSampleLength, complex_d{});
forward_fft(al::span{fftBuffer2});
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>(fftBuffer2[i].real()) / float{sFftLength};
fftTmp[i*2 + 1] = static_cast<float>((i == 0) ? fftBuffer2[sSampleLength].real()
: fftBuffer2[i].imag()) / float{sFftLength};
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(), filter, PFFFT_BACKWARD);
mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD);
filter += sFftLength;
}
}
@@ -133,11 +130,10 @@ constexpr std::array<float,4> Filter2Coeff{{
0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f
}};
} // namespace
void UhjAllPassFilter::processOne(const al::span<const float, 4> coeffs, float x)
void processOne(UhjAllPassFilter &self, const al::span<const float, 4> coeffs, float x)
{
auto state = mState;
auto state = self.mState;
for(size_t i{0};i < 4;++i)
{
const float y{x*coeffs[i] + state[i].z[0]};
@@ -145,13 +141,13 @@ void UhjAllPassFilter::processOne(const al::span<const float, 4> coeffs, float x
state[i].z[1] = y*coeffs[i] - x;
x = y;
}
mState = state;
self.mState = state;
}
void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
const al::span<const float> src, const bool updateState, float *RESTRICT dst)
void process(UhjAllPassFilter &self, const al::span<const float,4> coeffs,
const al::span<const float> src, const bool updateState, const al::span<float> dst)
{
auto state = mState;
auto state = self.mState;
auto proc_sample = [&state,coeffs](float x) noexcept -> float
{
@@ -164,10 +160,11 @@ void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
}
return x;
};
std::transform(src.begin(), src.end(), dst, proc_sample);
if(updateState) LIKELY mState = state;
std::transform(src.begin(), src.end(), dst.begin(), proc_sample);
if(updateState) LIKELY self.mState = state;
}
} // namespace
/* Encoding UHJ from B-Format is done as:
*
@@ -196,32 +193,39 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
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. */
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);
/* Some Clang versions don't like calling subspan on an rvalue here. */
const auto wxio_ = al::span{mWXInOut};
auto wxio = wxio_.subspan(mFifoPos, todo);
/* Copy out the samples that were previously processed by the FFT. */
std::copy_n(mWXInOut.begin()+mFifoPos, todo, mD.begin()+base);
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(winput+base, winput+base+todo, xinput+base, mWXInOut.begin()+mFifoPos,
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; });
@@ -235,27 +239,30 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
/* Copy the new input to the next history segment, clearing the back
* half of the segment, and convert to the frequency domain.
*/
float *input{mWXHistory.data() + curseg*sFftLength};
auto input = mWXHistory.begin() + curseg*sFftLength;
std::copy_n(mWXInOut.begin(), sSegmentSize, input);
std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f);
Filter.mFft.transform(input, input, mWorkData.data(), PFFFT_FORWARD);
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);
const float *filter{Filter.mFilterData.data()};
auto filter = Filter.mFilterData.begin();
for(size_t s{curseg};s < sNumSegments;++s)
{
Filter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.data());
Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += sFftLength;
filter += sFftLength;
}
input = mWXHistory.data();
input = mWXHistory.begin();
for(size_t s{0};s < curseg;++s)
{
Filter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.data());
Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += sFftLength;
filter += sFftLength;
}
@@ -266,10 +273,9 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(),
PFFFT_BACKWARD);
for(size_t i{0};i < sSegmentSize;++i)
mWXInOut[i] = mFftBuffer[i] + mWXInOut[sSegmentSize+i];
for(size_t i{0};i < sSegmentSize;++i)
mWXInOut[sSegmentSize+i] = mFftBuffer[sSegmentSize+i];
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);
@@ -277,8 +283,8 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
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());
@@ -286,35 +292,35 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.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;
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;
}
@@ -337,47 +343,49 @@ 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);
process(mFilter1WX, 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());
process(mFilter2WX, 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);
process(mFilter1Y, 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);
const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo};
process(mFilter1Direct[0], Filter1Coeff, left, 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++)
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);
const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo};
process(mFilter1Direct[1], Filter1Coeff, right, 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++)
for(size_t i{0};i < SamplesToDo;++i)
right[i] = (mS[i] - mD[i])*0.5f + mTemp[i];
}
@@ -404,28 +412,26 @@ void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesT
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());
@@ -433,21 +439,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)
@@ -455,10 +462,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; });
}
}
@@ -468,65 +475,65 @@ 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+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{});
}
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()+sInputPadding+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; });
if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]);
mFilter2DT.process(Filter2Coeff, {mTemp.data()+1, samplesToDo}, updateState, xoutput);
if(mFirstRun) processOne(mFilter2DT, Filter2Coeff, mTemp[0]);
process(mFilter2DT, Filter2Coeff, al::span{mTemp}.subspan(1, samplesToDo), updateState,
xoutput);
/* Apply filter1 to S and store in mTemp. */
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data());
process(mFilter1S, 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; });
mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data());
process(mFilter1DT, Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp);
/* Precompute j*S and store in youtput. */
if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput);
if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]);
process(mFilter2S, 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. */
mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data());
process(mFilter1Q, 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;
@@ -538,9 +545,9 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
* S = Left + Right
* D = Left - Right
*
* W = 0.6098637*S - 0.6896511*j*w*D
* X = 0.8624776*S + 0.7626955*j*w*D
* Y = 1.6822415*w*D - 0.2156194*j*S
* W = 0.6098637*S + 0.6896511*j*w*D
* X = 0.8624776*S - 0.7626955*j*w*D
* Y = 1.6822415*w*D + 0.2156194*j*S
*
* where j is a +90 degree phase shift. w is a variable control for the
* resulting stereo width, with the range 0 <= w <= 0.7.
@@ -554,13 +561,13 @@ void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t sa
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.
@@ -569,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];
/* 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];
/* W = 0.6098637*S + 0.6896511*j*w*D */
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 */
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];
/* Y = 1.6822415*w*D + 0.2156194*j*S */
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,
@@ -624,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+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.
@@ -639,53 +653,61 @@ 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+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};
/* Apply filter1 to S and store in mTemp. */
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data());
process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp);
/* Precompute j*D and store in xoutput. */
if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]);
mFilter2D.process(Filter2Coeff, {mD.data()+1, samplesToDo}, updateState, xoutput);
if(mFirstRun) processOne(mFilter2D, Filter2Coeff, mD[0]);
process(mFilter2D, 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];
/* 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];
/* W = 0.6098637*S + 0.6896511*j*w*D */
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 */
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. */
if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput);
if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]);
process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput);
/* Apply filter1 to D and store in mTemp. */
mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data());
process(mFilter1D, 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];
/* Y = 1.6822415*w*D + 0.2156194*j*S */
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;
}
+24 -15
View File
@@ -2,11 +2,12 @@
#define CORE_UHJFILTER_H
#include <array>
#include <cstddef>
#include <cstdint>
#include "almalloc.h"
#include "alspan.h"
#include "bufferline.h"
#include "opthelpers.h"
inline constexpr std::size_t UhjLength256{256};
@@ -19,8 +20,8 @@ enum class UhjQualityType : std::uint8_t {
Default = IIR
};
extern UhjQualityType UhjDecodeQuality;
extern UhjQualityType UhjEncodeQuality;
inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
struct UhjAllPassFilter {
@@ -29,16 +30,18 @@ struct UhjAllPassFilter {
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);
};
struct UhjEncoderBase {
struct SIMDALIGN UhjEncoderBase {
UhjEncoderBase() = default;
UhjEncoderBase(const UhjEncoderBase&) = delete;
UhjEncoderBase(UhjEncoderBase&&) = delete;
virtual ~UhjEncoderBase() = default;
void operator=(const UhjEncoderBase&) = delete;
void operator=(UhjEncoderBase&&) = delete;
virtual std::size_t getDelay() noexcept = 0;
/**
@@ -82,7 +85,7 @@ 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 std::size_t SamplesToDo) override;
const std::size_t SamplesToDo) final;
};
struct UhjEncoderIIR final : public UhjEncoderBase {
@@ -110,19 +113,25 @@ 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 std::size_t SamplesToDo) override;
const std::size_t SamplesToDo) final;
};
struct DecoderBase {
struct SIMDALIGN DecoderBase {
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;
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;
@@ -156,7 +165,7 @@ struct UhjDecoder final : public DecoderBase {
* B-Format decoder, as it needs different shelf filters.
*/
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
const bool updateState) override;
const bool updateState) final;
};
struct UhjDecoderIIR final : public DecoderBase {
@@ -180,7 +189,7 @@ struct UhjDecoderIIR final : public DecoderBase {
UhjAllPassFilter mFilter1Q;
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
const bool updateState) override;
const bool updateState) final;
};
template<std::size_t N>
@@ -204,7 +213,7 @@ struct UhjStereoDecoder final : public DecoderBase {
* channels, and the third left empty.
*/
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
const bool updateState) override;
const bool updateState) final;
};
struct UhjStereoDecoderIIR final : public DecoderBase {
@@ -223,7 +232,7 @@ struct UhjStereoDecoderIIR final : public DecoderBase {
UhjAllPassFilter mFilter2S;
void decode(const al::span<float*> samples, const std::size_t samplesToDo,
const bool updateState) override;
const bool updateState) final;
};
#endif /* CORE_UHJFILTER_H */
+3 -7
View File
@@ -1,17 +1,13 @@
#include "config.h"
#include "config_backends.h"
#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);
@@ -20,7 +16,7 @@ DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x0
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP)
#if HAVE_WASAPI && !ALSOFT_UWP
#include <wtypes.h>
#include <devpropdef.h>
#include <propkeydef.h>
+169 -172
View File
@@ -1,5 +1,6 @@
#include "config.h"
#include "config_simd.h"
#include "voice.h"
@@ -42,10 +43,10 @@
#include "voice_change.h"
struct CTag;
#ifdef HAVE_SSE
#if HAVE_SSE
struct SSETag;
#endif
#ifdef HAVE_NEON
#if HAVE_NEON
struct NEONTag;
#endif
@@ -57,30 +58,29 @@ 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!");
Resampler ResamplerDefault{Resampler::Cubic};
namespace {
using uint = unsigned int;
using namespace std::chrono;
using namespace std::string_view_literals;
using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize,
const MixHrtfFilter *hrtfparams, const size_t BufferSize);
using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples,
const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
const size_t BufferSize);
using HrtfMixerFunc = void(*)(const al::span<const float> InSamples,
const al::span<float2> AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams,
const size_t SamplesToDo);
using HrtfMixerBlendFunc = void(*)(const al::span<const float> InSamples,
const al::span<float2> AccumSamples, const uint IrSize, const HrtfFilter *oldparams,
const MixHrtfFilter *newparams, const size_t SamplesToDo);
HrtfMixerFunc MixHrtfSamples{MixHrtf_<CTag>};
HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_<CTag>};
inline MixerOutFunc SelectMixer()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Mix_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Mix_<SSETag>;
#endif
@@ -89,11 +89,11 @@ inline MixerOutFunc SelectMixer()
inline MixerOneFunc SelectMixerOne()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Mix_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Mix_<SSETag>;
#endif
@@ -102,11 +102,11 @@ inline MixerOneFunc SelectMixerOne()
inline HrtfMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtf_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtf_<SSETag>;
#endif
@@ -115,11 +115,11 @@ inline HrtfMixerFunc SelectHrtfMixer()
inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtfBlend_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtfBlend_<SSETag>;
#endif
@@ -140,31 +140,40 @@ void Voice::InitMixer(std::optional<std::string> resopt)
ResamplerEntry{"none"sv, Resampler::Point},
ResamplerEntry{"point"sv, Resampler::Point},
ResamplerEntry{"linear"sv, Resampler::Linear},
ResamplerEntry{"cubic"sv, Resampler::Cubic},
ResamplerEntry{"spline"sv, Resampler::Spline},
ResamplerEntry{"gaussian"sv, Resampler::Gaussian},
ResamplerEntry{"bsinc12"sv, Resampler::BSinc12},
ResamplerEntry{"fast_bsinc12"sv, Resampler::FastBSinc12},
ResamplerEntry{"bsinc24"sv, Resampler::BSinc24},
ResamplerEntry{"fast_bsinc24"sv, Resampler::FastBSinc24},
ResamplerEntry{"bsinc48"sv, Resampler::BSinc48},
ResamplerEntry{"fast_bsinc48"sv, Resampler::FastBSinc48},
};
std::string_view resampler{*resopt};
if(al::case_compare(resampler, "bsinc"sv) == 0)
if (al::case_compare(resampler, "cubic"sv) == 0)
{
WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str());
resampler = "bsinc12"sv;
WARN("Resampler option \"{}\" is deprecated, using spline", *resopt);
resampler = "spline"sv;
}
else if(al::case_compare(resampler, "sinc4"sv) == 0
|| al::case_compare(resampler, "sinc8"sv) == 0)
{
WARN("Resampler option \"%s\" is deprecated, using cubic\n", resopt->c_str());
resampler = "cubic"sv;
WARN("Resampler option \"{}\" is deprecated, using gaussian", *resopt);
resampler = "gaussian"sv;
}
else if(al::case_compare(resampler, "bsinc"sv) == 0)
{
WARN("Resampler option \"{}\" is deprecated, using bsinc12", *resopt);
resampler = "bsinc12"sv;
}
auto iter = std::find_if(ResamplerList.begin(), ResamplerList.end(),
[resampler](const ResamplerEntry &entry) -> bool
{ return al::case_compare(resampler, entry.name) == 0; });
if(iter == ResamplerList.end())
ERR("Invalid resampler: %s\n", resopt->c_str());
ERR("Invalid resampler: {}", *resopt);
else
ResamplerDefault = iter->resampler;
}
@@ -198,7 +207,7 @@ constexpr std::array<int,16> IMA4Codeword{{
}};
/* IMA4 ADPCM Step index adjust decode table */
constexpr std::array<int,16>IMA4Index_adjust{{
constexpr std::array<int,16> IMA4Index_adjust{{
-1,-1,-1,-1, 2, 4, 6, 8,
-1,-1,-1,-1, 2, 4, 6, 8
}};
@@ -225,9 +234,9 @@ void SendSourceStoppedEvent(ContextBase *context, uint id)
{
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
if(evt_vec[0].len < 1) return;
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec[0].buf);
evt.mId = id;
evt.mState = AsyncSrcState::Stop;
@@ -269,18 +278,17 @@ inline void LoadSamples(const al::span<float> dstSamples, const al::span<const s
{
using TypeTraits = al::FmtTypeTraits<Type>;
using SampleType = typename TypeTraits::Type;
static constexpr size_t sampleSize{sizeof(SampleType)};
assert(srcChan < srcStep);
auto converter = TypeTraits{};
al::span<const SampleType> src{reinterpret_cast<const SampleType*>(srcData.data()),
srcData.size()/sampleSize};
auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep);
std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter]
srcData.size()/sizeof(SampleType)};
auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep + srcChan);
dstSamples.front() = converter(*ssrc);
std::generate(dstSamples.begin()+1, dstSamples.end(), [&ssrc,srcStep,converter]
{
auto ret = converter(ssrc[srcChan]);
ssrc += ptrdiff_t(srcStep);
return ret;
return converter(*ssrc);
});
}
@@ -302,46 +310,53 @@ inline void LoadSamples<FmtIMA4>(al::span<float> dstSamples, al::span<const std:
size_t skip{srcOffset % samplesPerBlock};
/* NOTE: This could probably be optimized better. */
while(!dstSamples.empty())
auto dst = dstSamples.begin();
while(dst != dstSamples.end())
{
auto nibbleData = src.cbegin();
src = src.subspan(blockBytes);
/* Each IMA4 block starts with a signed 16-bit sample, and a signed
/* Each IMA4 block starts with a signed 16-bit sample, and a signed(?)
* 16-bit table index. The table index needs to be clamped.
*/
int sample{int(nibbleData[srcChan*4]) | (int(nibbleData[srcChan*4 + 1]) << 8)};
int index{int(nibbleData[srcChan*4 + 2]) | (int(nibbleData[srcChan*4 + 3]) << 8)};
nibbleData += ptrdiff_t((srcStep+srcChan)*4);
auto prevSample = int(src[srcChan*4 + 0]) | (int(src[srcChan*4 + 1]) << 8);
auto prevIndex = int(src[srcChan*4 + 2]) | (int(src[srcChan*4 + 3]) << 8);
const auto nibbleData = src.subspan((srcStep+srcChan)*4);
src = src.subspan(blockBytes);
sample = (sample^0x8000) - 32768;
index = std::clamp((index^0x8000) - 32768, 0, MaxStepIndex);
/* Sign-extend the 16-bit sample and index values. */
prevSample = (prevSample^0x8000) - 32768;
prevIndex = std::clamp((prevIndex^0x8000) - 32768, 0, MaxStepIndex);
if(skip == 0)
{
dstSamples[0] = static_cast<float>(sample) / 32768.0f;
dstSamples = dstSamples.subspan<1>();
if(dstSamples.empty()) return;
*dst = static_cast<float>(prevSample) / 32768.0f;
if(++dst == dstSamples.end()) return;
}
else
--skip;
auto decode_sample = [&sample,&index](const uint nibble)
{
sample += IMA4Codeword[nibble] * IMAStep_size[static_cast<uint>(index)] / 8;
sample = std::clamp(sample, -32768, 32767);
index += IMA4Index_adjust[nibble];
index = std::clamp(index, 0, MaxStepIndex);
return sample;
};
/* The rest of the block is arranged as a series of nibbles, contained
* in 4 *bytes* per channel interleaved. So every 8 nibbles we need to
* skip 4 bytes per channel to get the next nibbles for this channel.
*
* First, decode the samples that we need to skip in the block (will
*/
auto decode_nibble = [&prevSample,&prevIndex,srcStep,nibbleData](const size_t nibbleOffset)
noexcept -> int
{
static constexpr auto NibbleMask = std::byte{0xf};
const auto byteShift = (nibbleOffset&1) * 4;
const auto wordOffset = (nibbleOffset>>1) & ~3_uz;
const auto byteOffset = wordOffset*srcStep + ((nibbleOffset>>1)&3);
const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask);
prevSample += IMA4Codeword[nibble] * IMAStep_size[static_cast<uint>(prevIndex)] / 8;
prevSample = std::clamp(prevSample, -32768, 32767);
prevIndex += IMA4Index_adjust[nibble];
prevIndex = std::clamp(prevIndex, 0, MaxStepIndex);
return prevSample;
};
/* First, decode the samples that we need to skip in the block (will
* always be less than the block size). They need to be decoded despite
* being ignored for proper state on the remaining samples.
*/
@@ -349,29 +364,22 @@ inline void LoadSamples<FmtIMA4>(al::span<float> dstSamples, al::span<const std:
const size_t startOffset{skip + 1};
for(;skip;--skip)
{
const size_t byteShift{(nibbleOffset&1) * 4};
const size_t wordOffset{(nibbleOffset>>1) & ~3_uz};
const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)};
std::ignore = decode_nibble(nibbleOffset);
++nibbleOffset;
std::ignore = decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u);
}
/* Second, decode the rest of the block and write to the output, until
* the end of the block or the end of output.
*/
const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())};
std::generate_n(dstSamples.begin(), todo, [&]
const auto todo = std::min(samplesPerBlock - startOffset,
size_t(std::distance(dst, dstSamples.end())));
dst = std::generate_n(dst, todo, [&]
{
const size_t byteShift{(nibbleOffset&1) * 4};
const size_t wordOffset{(nibbleOffset>>1) & ~3_uz};
const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)};
const auto sample = decode_nibble(nibbleOffset);
++nibbleOffset;
const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)};
return static_cast<float>(result) / 32768.0f;
return static_cast<float>(sample) / 32768.0f;
});
dstSamples = dstSamples.subspan(todo);
}
}
@@ -388,30 +396,27 @@ inline void LoadSamples<FmtMSADPCM>(al::span<float> dstSamples, al::span<const s
src = src.subspan(srcOffset/samplesPerBlock*blockBytes);
size_t skip{srcOffset % samplesPerBlock};
while(!dstSamples.empty())
auto dst = dstSamples.begin();
while(dst != dstSamples.end())
{
auto input = src.cbegin();
src = src.subspan(blockBytes);
/* Each MS ADPCM block starts with an 8-bit block predictor, used to
* dictate how the two sample history values are mixed with the decoded
* sample, and an initial signed 16-bit delta value which scales the
* sample, and an initial signed 16-bit scaling value which scales the
* nibble sample value. This is followed by the two initial 16-bit
* sample history values.
*/
const uint8_t blockpred{std::min(uint8_t(input[srcChan]), uint8_t{6})};
input += ptrdiff_t(srcStep);
int delta{int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1]) << 8)};
input += ptrdiff_t(srcStep*2);
const auto blockpred = std::min(uint8_t(src[srcChan]),
uint8_t{MSADPCMAdaptionCoeff.size()-1});
auto scale = int(src[srcStep + 2*srcChan + 0]) | (int(src[srcStep + 2*srcChan + 1]) << 8);
std::array<int,2> sampleHistory{};
sampleHistory[0] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8);
input += ptrdiff_t(srcStep*2);
sampleHistory[1] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8);
input += ptrdiff_t(srcStep*2);
auto sampleHistory = std::array{
int(src[3*srcStep + 2*srcChan + 0]) | (int(src[3*srcStep + 2*srcChan + 1])<<8),
int(src[5*srcStep + 2*srcChan + 0]) | (int(src[5*srcStep + 2*srcChan + 1])<<8)};
const auto nibbleData = src.subspan(7*srcStep);
src = src.subspan(blockBytes);
const al::span coeffs{MSADPCMAdaptionCoeff[blockpred]};
delta = (delta^0x8000) - 32768;
const auto coeffs = al::span{MSADPCMAdaptionCoeff[blockpred]};
scale = (scale^0x8000) - 32768;
sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768;
sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768;
@@ -420,66 +425,66 @@ inline void LoadSamples<FmtMSADPCM>(al::span<float> dstSamples, al::span<const s
*/
if(skip == 0)
{
dstSamples[0] = static_cast<float>(sampleHistory[1]) / 32768.0f;
dstSamples = dstSamples.subspan<1>();
if(dstSamples.empty()) return;
dstSamples[0] = static_cast<float>(sampleHistory[0]) / 32768.0f;
dstSamples = dstSamples.subspan<1>();
if(dstSamples.empty()) return;
*dst = static_cast<float>(sampleHistory[1]) / 32768.0f;
if(++dst == dstSamples.end()) return;
*dst = static_cast<float>(sampleHistory[0]) / 32768.0f;
if(++dst == dstSamples.end()) return;
}
else if(skip == 1)
{
--skip;
dstSamples[0] = static_cast<float>(sampleHistory[0]) / 32768.0f;
dstSamples = dstSamples.subspan<1>();
if(dstSamples.empty()) return;
*dst = static_cast<float>(sampleHistory[0]) / 32768.0f;
if(++dst == dstSamples.end()) return;
}
else
skip -= 2;
auto decode_sample = [&sampleHistory,&delta,coeffs](const int nibble)
/* The rest of the block is a series of nibbles, interleaved per-
* channel.
*/
auto decode_nibble = [&sampleHistory,&scale,coeffs,nibbleData](const size_t nibbleOffset)
noexcept -> int
{
int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256};
pred += ((nibble^0x08) - 0x08) * delta;
pred = std::clamp(pred, -32768, 32767);
static constexpr auto NibbleMask = std::byte{0xf};
const auto byteOffset = nibbleOffset>>1;
const auto byteShift = ((nibbleOffset&1)^1) * 4;
const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask);
const auto pred = ((nibble^0x08) - 0x08) * scale;
const auto diff = (sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256;
const auto sample = std::clamp(pred + diff, -32768, 32767);
sampleHistory[1] = sampleHistory[0];
sampleHistory[0] = pred;
sampleHistory[0] = sample;
delta = (MSADPCMAdaption[static_cast<uint>(nibble)] * delta) / 256;
delta = std::max(16, delta);
scale = MSADPCMAdaption[nibble] * scale / 256;
scale = std::max(16, scale);
return pred;
return sample;
};
/* The rest of the block is a series of nibbles, interleaved per-
* channel. First, skip samples.
*/
/* First, skip samples. */
const size_t startOffset{skip + 2};
size_t nibbleOffset{srcChan};
for(;skip;--skip)
{
const size_t byteOffset{nibbleOffset>>1};
const size_t byteShift{((nibbleOffset&1)^1) * 4};
std::ignore = decode_nibble(nibbleOffset);
nibbleOffset += srcStep;
std::ignore = decode_sample(int(input[byteOffset]>>byteShift) & 15);
}
/* Now decode the rest of the block, until the end of the block or the
* dst buffer is filled.
*/
const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())};
std::generate_n(dstSamples.begin(), todo, [&]
const auto todo = std::min(samplesPerBlock - startOffset,
size_t(std::distance(dst, dstSamples.end())));
dst = std::generate_n(dst, todo, [&]
{
const size_t byteOffset{nibbleOffset>>1};
const size_t byteShift{((nibbleOffset&1)^1) * 4};
const auto sample = decode_nibble(nibbleOffset);
nibbleOffset += srcStep;
const int sample{decode_sample(int(input[byteOffset]>>byteShift) & 15)};
return static_cast<float>(sample) / 32768.0f;
});
dstSamples = dstSamples.subspan(todo);
}
}
@@ -607,7 +612,7 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
void DoHrtfMix(const al::span<const float> samples, DirectParams &parms, const float TargetGain,
const size_t Counter, uint OutPos, const bool IsPlaying, DeviceBase *Device)
const size_t Counter, size_t OutPos, const bool IsPlaying, DeviceBase *Device)
{
const uint IrSize{Device->mIrSize};
const auto HrtfSamples = al::span{Device->ExtraSampleData};
@@ -619,8 +624,10 @@ void DoHrtfMix(const al::span<const float> samples, DirectParams &parms, const f
std::copy_n(samples.begin(), samples.size(), src_iter);
/* Copy the last used samples back into the history buffer for later. */
if(IsPlaying) LIKELY
std::copy_n(HrtfSamples.begin() + ptrdiff_t(samples.size()), parms.Hrtf.History.size(),
parms.Hrtf.History.begin());
{
const auto endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size());
std::copy_n(endsamples.cbegin(), endsamples.size(), parms.Hrtf.History.begin());
}
/* If fading and this is the first mixing pass, fade between the IRs. */
size_t fademix{0};
@@ -645,8 +652,8 @@ void DoHrtfMix(const al::span<const float> samples, DirectParams &parms, const f
parms.Hrtf.Target.Coeffs,
parms.Hrtf.Target.Delay,
0.0f, gain / static_cast<float>(fademix)};
MixHrtfBlendSamples(HrtfSamples.data(), AccumSamples.data()+OutPos, IrSize,
&parms.Hrtf.Old, &hrtfparams, fademix);
MixHrtfBlendSamples(HrtfSamples, AccumSamples.subspan(OutPos), IrSize, &parms.Hrtf.Old,
&hrtfparams, fademix);
/* Update the old parameters with the result. */
parms.Hrtf.Old = parms.Hrtf.Target;
@@ -673,8 +680,8 @@ void DoHrtfMix(const al::span<const float> samples, DirectParams &parms, const f
parms.Hrtf.Target.Delay,
parms.Hrtf.Old.Gain,
(gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo)};
MixHrtfSamples(HrtfSamples.data()+fademix, AccumSamples.data()+OutPos, IrSize, &hrtfparams,
todo);
MixHrtfSamples(HrtfSamples.subspan(fademix), AccumSamples.subspan(OutPos), IrSize,
&hrtfparams, todo);
/* Store the now-current gain for next time. */
parms.Hrtf.Old.Gain = gain;
@@ -682,32 +689,31 @@ void DoHrtfMix(const al::span<const float> samples, DirectParams &parms, const f
}
void DoNfcMix(const al::span<const float> samples, al::span<FloatBufferLine> OutBuffer,
DirectParams &parms, const float *TargetGains, const uint Counter, const uint OutPos,
DeviceBase *Device)
DirectParams &parms, const al::span<const float,MaxOutputChannels> OutGains,
const uint Counter, const uint OutPos, DeviceBase *Device)
{
using FilterProc = void (NfcFilter::*)(const al::span<const float>, const al::span<float>);
static constexpr std::array<FilterProc,MaxAmbiOrder+1> NfcProcess{{
nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}};
auto CurrentGains = parms.Gains.Current.begin();
MixSamples(samples, OutBuffer.first<1>(), al::to_address(CurrentGains), TargetGains, Counter,
OutPos);
OutBuffer = OutBuffer.subspan<1>();
++CurrentGains;
++TargetGains;
MixSamples(samples, al::span{OutBuffer[0]}.subspan(OutPos), parms.Gains.Current[0],
OutGains[0], Counter);
OutBuffer = OutBuffer.subspan(1);
auto CurrentGains = al::span{parms.Gains.Current}.subspan(1);
auto TargetGains = OutGains.subspan(1);
const auto nfcsamples = al::span{Device->ExtraSampleData.begin(), samples.size()};
const auto nfcsamples = al::span{Device->ExtraSampleData}.first(samples.size());
size_t order{1};
while(const size_t chancount{Device->NumChannelsPerOrder[order]})
{
(parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples);
MixSamples(nfcsamples, OutBuffer.first(chancount), al::to_address(CurrentGains),
TargetGains, Counter, OutPos);
OutBuffer = OutBuffer.subspan(chancount);
CurrentGains += chancount;
TargetGains += chancount;
MixSamples(nfcsamples, OutBuffer.first(chancount), CurrentGains, TargetGains, Counter,
OutPos);
if(++order == MaxAmbiOrder+1)
break;
OutBuffer = OutBuffer.subspan(chancount);
CurrentGains = CurrentGains.subspan(chancount);
TargetGains = TargetGains.subspan(chancount);
}
}
@@ -770,17 +776,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
/* Get the number of samples ahead of the current time that output
* should start at. Skip this update if it's beyond the output sample
* count.
*
* Round the start position to a multiple of 4, which some mixers want.
* This makes the start time accurate to 4 samples. This could be made
* sample-accurate by forcing non-SIMD functions on the first run.
*/
seconds::rep sampleOffset{duration_cast<seconds>(diff * Device->Frequency).count()};
sampleOffset = (sampleOffset+2) & ~seconds::rep{3};
if(sampleOffset >= SamplesToDo)
return;
OutPos = static_cast<uint>(sampleOffset);
OutPos = static_cast<uint>(round<seconds>(diff * Device->mSampleRate).count());
if(OutPos >= SamplesToDo) return;
}
/* Calculate the number of samples to mix, and the number of (resampled)
@@ -792,8 +790,8 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
/* Get a span of pointers to hold the floating point, deinterlaced,
* resampled buffer data to be mixed.
*/
std::array<float*,DeviceBase::MixerChannelsMax> SamplePointers;
const al::span<float*> MixingSamples{SamplePointers.data(), mChans.size()};
auto SamplePointers = std::array<float*,DeviceBase::MixerChannelsMax>{};
const auto MixingSamples = al::span{SamplePointers}.first(mChans.size());
{
const uint channelStep{(samplesToLoad+3u)&~3u};
auto base = Device->mSampleData.end() - MixingSamples.size()*channelStep;
@@ -851,7 +849,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
dataSize64 += ext + MaxResamplerEdge;
if(dataSize64 <= srcSizeMax)
return std::make_pair(dstBufferSize, static_cast<uint>(dataSize64));
return std::array{dstBufferSize, static_cast<uint>(dataSize64)};
/* If the source size got saturated, we can't fill the desired
* dst size. Figure out how many dst samples we can fill.
@@ -866,7 +864,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
*/
dstBufferSize = static_cast<uint>(dataSize64) & ~3u;
}
return std::make_pair(dstBufferSize, srcSizeMax);
return std::array{dstBufferSize, srcSizeMax};
};
const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes(
samplesToLoad - samplesLoaded);
@@ -896,17 +894,16 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
{
const uint avail{std::min(srcBufferSize, MaxResamplerEdge)};
const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)};
const auto srcbuf = resampleBuffer.first(tofill);
/* When loading from a voice that ended prematurely, only take
* the samples that get closest to 0 amplitude. This helps
* certain sounds fade out better.
*/
auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
{ return std::abs(lhs) < std::abs(rhs); };
auto srciter = std::min_element(resampleBuffer.begin(),
resampleBuffer.begin()+ptrdiff_t(avail), abs_lt);
auto srciter = std::min_element(srcbuf.begin(), srcbuf.begin()+ptrdiff_t(avail),
[](const float l, const float r) { return std::abs(l) < std::abs(r); });
std::fill(srciter+1, resampleBuffer.begin()+ptrdiff_t(tofill), *srciter);
std::fill(srciter+1, srcbuf.end(), *srciter);
}
else if(mFlags.test(VoiceIsStatic))
{
@@ -962,7 +959,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
std::copy_n(resampleBuffer.cbegin(), dstBufferSize,
MixingSamples[chan]+samplesLoaded);
else
mResampler(&mResampleState, resampleBuffer.data(), fracPos, increment,
mResampler(&mResampleState, Device->mResampleData, fracPos, increment,
{MixingSamples[chan]+samplesLoaded, dstBufferSize});
/* Store the last source samples used for next time. */
@@ -1066,13 +1063,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
}
else
{
const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data()
: SilentTarget.data()};
const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target}
: al::span{SilentTarget};
if(mFlags.test(VoiceHasNfc))
DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device);
else
MixSamples(samples, mDirect.Buffer, parms.Gains.Current.data(), TargetGains,
Counter, OutPos);
MixSamples(samples, mDirect.Buffer, parms.Gains.Current, TargetGains, Counter,
OutPos);
}
}
@@ -1085,10 +1082,10 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf,
{*voiceSamples, samplesToMix}, mSend[send].FilterType);
const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data()
: SilentTarget.data()};
MixSamples(samples, mSend[send].Buffer, parms.Gains.Current.data(), TargetGains,
Counter, OutPos);
const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target}
: al::span{SilentTarget};
MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, TargetGains, Counter,
OutPos);
}
++voiceSamples;
@@ -1191,9 +1188,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
{
RingBuffer *ring{Context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0)
if(evt_vec[0].len > 0)
{
auto &evt = InitAsyncEvent<AsyncBufferCompleteEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncBufferCompleteEvent>(evt_vec[0].buf);
evt.mId = SourceID;
evt.mCount = buffers_done;
ring->writeAdvance(1);
@@ -1221,7 +1218,7 @@ void Voice::prepare(DeviceBase *device)
: ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))};
if(num_channels > device->MixerChannelsMax) UNLIKELY
{
ERR("Unexpected channel count: %u (limit: %zu, %s : %d)\n", num_channels,
ERR("Unexpected channel count: {} (limit: {}, {} : {})", num_channels,
device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder);
num_channels = device->MixerChannelsMax;
}
@@ -1296,7 +1293,7 @@ void Voice::prepare(DeviceBase *device)
* Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is
* identity, so don't mess with it).
*/
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->mSampleRate)};
for(auto &chandata : mChans)
{
chandata.mAmbiHFScale = 1.0f;
@@ -1323,7 +1320,7 @@ void Voice::prepare(DeviceBase *device)
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder,
device->m2DMixing);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->mSampleRate)};
for(auto &chandata : mChans)
{
chandata.mAmbiHFScale = scales[*(OrderFromChan++)];
+9 -6
View File
@@ -10,7 +10,6 @@
#include <optional>
#include <string>
#include "almalloc.h"
#include "alspan.h"
#include "bufferline.h"
#include "buffer_storage.h"
@@ -20,6 +19,7 @@
#include "filters/splitter.h"
#include "mixer/defs.h"
#include "mixer/hrtfdefs.h"
#include "opthelpers.h"
#include "resampler_limits.h"
#include "uhjfilter.h"
#include "vector.h"
@@ -102,7 +102,10 @@ struct VoiceBufferItem {
uint mLoopStart{0u};
uint mLoopEnd{0u};
al::span<std::byte> mSamples{};
al::span<std::byte> mSamples;
protected:
~VoiceBufferItem() = default;
};
@@ -180,7 +183,7 @@ enum : uint {
VoiceFlagCount
};
struct Voice {
struct SIMDALIGN Voice {
enum State {
Stopped,
Playing,
@@ -233,9 +236,9 @@ struct Voice {
ResamplerFunc mResampler{};
InterpState mResampleState{};
InterpState mResampleState;
std::bitset<VoiceFlagCount> mFlags{};
std::bitset<VoiceFlagCount> mFlags;
uint mNumCallbackBlocks{0};
uint mCallbackBlockBase{0};
@@ -277,6 +280,6 @@ struct Voice {
static void InitMixer(std::optional<std::string> resopt);
};
extern Resampler ResamplerDefault;
inline Resampler ResamplerDefault{Resampler::Spline};
#endif /* CORE_VOICE_H */