mirror of
https://github.com/love2d/megasource.git
synced 2026-08-19 12:14:41 +02:00
update OpenAL-Soft to 1.24.3.
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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,
|
||||
}};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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,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};
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
|
||||
extern int CPUCapFlags;
|
||||
inline int CPUCapFlags{0};
|
||||
enum {
|
||||
CPU_CAP_SSE = 1<<0,
|
||||
CPU_CAP_SSE2 = 1<<1,
|
||||
|
||||
@@ -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,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,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; \
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)}
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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, ¶m);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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++)];
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user