mirror of
https://github.com/love2d/megasource.git
synced 2026-08-25 06:55:06 +02:00
update OpenAL-Soft to 1.24.3.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
|
||||
#include "alassert.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]]
|
||||
void throw_error(const std::string &message)
|
||||
{
|
||||
throw std::runtime_error{message};
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
namespace al {
|
||||
|
||||
[[noreturn]]
|
||||
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept
|
||||
{
|
||||
/* Throwing an exception that tries to leave a noexcept function will
|
||||
* hopefully cause the system to provide info about the caught exception in
|
||||
* an error dialog. At least on Linux, this results in the process printing
|
||||
*
|
||||
* terminate called after throwing an instance of 'std::runtime_error'
|
||||
* what(): <message here>
|
||||
*
|
||||
* before terminating from a SIGABRT. Hopefully Windows and Mac will do the
|
||||
* appropriate things with the message to alert the user about an abnormal
|
||||
* termination.
|
||||
*/
|
||||
auto errstr = std::string{filename};
|
||||
errstr += ':';
|
||||
errstr += std::to_string(linenum);
|
||||
errstr += ": ";
|
||||
errstr += funcname;
|
||||
errstr += ": ";
|
||||
errstr += message;
|
||||
|
||||
throw_error(errstr);
|
||||
}
|
||||
|
||||
} /* namespace al */
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef AL_ASSERT_H
|
||||
#define AL_ASSERT_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
[[noreturn]]
|
||||
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept;
|
||||
|
||||
} /* namespace al */
|
||||
|
||||
/* A custom assert macro that is not compiled out for Release/NDEBUG builds,
|
||||
* making it an appropriate replacement for assert() checks that must not be
|
||||
* ignored.
|
||||
*/
|
||||
#define alassert(cond) do { \
|
||||
if(!(cond)) UNLIKELY \
|
||||
al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \
|
||||
} while(0)
|
||||
|
||||
#endif /* AL_ASSERT_H */
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef AL_BIT_H
|
||||
#define AL_BIT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#ifndef __GNUC__
|
||||
#include <cstdint>
|
||||
@@ -25,6 +26,16 @@ To> bit_cast(const From &src) noexcept
|
||||
return *std::launder(reinterpret_cast<To*>(dst.data()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_integral_v<T>,
|
||||
T> byteswap(T value) noexcept
|
||||
{
|
||||
static_assert(std::has_unique_object_representations_v<T>);
|
||||
auto bytes = al::bit_cast<std::array<std::byte,sizeof(T)>>(value);
|
||||
std::reverse(bytes.begin(), bytes.end());
|
||||
return al::bit_cast<T>(bytes);
|
||||
}
|
||||
|
||||
#ifdef __BYTE_ORDER__
|
||||
enum class endian {
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace {
|
||||
|
||||
using ushort = unsigned short;
|
||||
using ushort2 = std::pair<ushort,ushort>;
|
||||
using ushort2 = std::array<ushort,2>;
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept
|
||||
@@ -56,8 +56,8 @@ struct BitReverser {
|
||||
|
||||
if(idx < revidx)
|
||||
{
|
||||
mData[ret_i].first = static_cast<ushort>(idx);
|
||||
mData[ret_i].second = static_cast<ushort>(revidx);
|
||||
mData[ret_i][0] = static_cast<ushort>(idx);
|
||||
mData[ret_i][1] = static_cast<ushort>(revidx);
|
||||
++ret_i;
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
|
||||
if(log2_size < gBitReverses.size()) LIKELY
|
||||
{
|
||||
for(auto &rev : gBitReverses[log2_size])
|
||||
std::swap(buffer[rev.first], buffer[rev.second]);
|
||||
std::swap(buffer[rev[0]], buffer[rev[1]]);
|
||||
|
||||
/* Iterative form of Danielson-Lanczos lemma */
|
||||
for(std::size_t i{0};i < log2_size;++i)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace gsl {
|
||||
template<typename T> using owner = T;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#define DISABLE_ALLOC \
|
||||
@@ -88,6 +88,9 @@ constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept
|
||||
{ return allocator<T,N>::Alignment != allocator<U,M>::Alignment; }
|
||||
|
||||
|
||||
#ifdef __cpp_lib_to_address
|
||||
using std::to_address;
|
||||
#else
|
||||
template<typename T>
|
||||
constexpr T *to_address(T *p) noexcept
|
||||
{
|
||||
@@ -100,7 +103,7 @@ constexpr auto to_address(const T &p) noexcept
|
||||
{
|
||||
return ::al::to_address(p.operator->());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
constexpr T* construct_at(T *ptr, Args&& ...args)
|
||||
@@ -120,31 +123,74 @@ class out_ptr_t {
|
||||
static_assert(!std::is_same_v<PT,void*>);
|
||||
|
||||
SP &mRes;
|
||||
std::variant<PT,void*> mPtr{};
|
||||
std::variant<PT,void*> mPtr;
|
||||
|
||||
public:
|
||||
out_ptr_t(SP &res) : mRes{res} { }
|
||||
~out_ptr_t()
|
||||
{
|
||||
auto set_res = [this](auto &ptr)
|
||||
{ mRes.reset(static_cast<PT>(ptr)); };
|
||||
std::visit(set_res, mPtr);
|
||||
}
|
||||
explicit out_ptr_t(SP &res) : mRes{res} { }
|
||||
~out_ptr_t() { std::visit([this](auto &ptr) { mRes.reset(static_cast<PT>(ptr)); }, mPtr); }
|
||||
|
||||
out_ptr_t() = delete;
|
||||
out_ptr_t(const out_ptr_t&) = delete;
|
||||
out_ptr_t& operator=(const out_ptr_t&) = delete;
|
||||
|
||||
operator PT*() noexcept
|
||||
operator PT*() noexcept /* NOLINT(google-explicit-constructor) */
|
||||
{ return &std::get<PT>(mPtr); }
|
||||
|
||||
operator void**() noexcept
|
||||
operator void**() noexcept /* NOLINT(google-explicit-constructor) */
|
||||
{ return &mPtr.template emplace<void*>(); }
|
||||
};
|
||||
|
||||
template<typename T=void, typename SP, typename ...Args>
|
||||
auto out_ptr(SP &res)
|
||||
auto out_ptr(SP &res, Args&& ...args)
|
||||
{
|
||||
using ptype = typename SP::element_type*;
|
||||
return out_ptr_t<SP,ptype>{res};
|
||||
static_assert(sizeof...(args) == 0);
|
||||
if constexpr(std::is_same_v<T,void>)
|
||||
{
|
||||
using ptype = typename SP::element_type*;
|
||||
return out_ptr_t<SP,ptype,Args...>{res};
|
||||
}
|
||||
else
|
||||
return out_ptr_t<SP,T,Args...>{res};
|
||||
}
|
||||
|
||||
|
||||
template<typename SP, typename PT, typename ...Args>
|
||||
class inout_ptr_t {
|
||||
static_assert(!std::is_same_v<PT,void*>);
|
||||
|
||||
SP &mRes;
|
||||
std::variant<PT,void*> mPtr;
|
||||
|
||||
public:
|
||||
explicit inout_ptr_t(SP &res) : mRes{res}, mPtr{res.get()} { }
|
||||
~inout_ptr_t()
|
||||
{
|
||||
mRes.release();
|
||||
std::visit([this](auto &ptr) { mRes.reset(static_cast<PT>(ptr)); }, mPtr);
|
||||
}
|
||||
|
||||
inout_ptr_t() = delete;
|
||||
inout_ptr_t(const inout_ptr_t&) = delete;
|
||||
inout_ptr_t& operator=(const inout_ptr_t&) = delete;
|
||||
|
||||
operator PT*() noexcept /* NOLINT(google-explicit-constructor) */
|
||||
{ return &std::get<PT>(mPtr); }
|
||||
|
||||
operator void**() noexcept /* NOLINT(google-explicit-constructor) */
|
||||
{ return &mPtr.template emplace<void*>(mRes.get()); }
|
||||
};
|
||||
|
||||
template<typename T=void, typename SP, typename ...Args>
|
||||
auto inout_ptr(SP &res, Args&& ...args)
|
||||
{
|
||||
static_assert(sizeof...(args) == 0);
|
||||
if constexpr(std::is_same_v<T,void>)
|
||||
{
|
||||
using ptype = typename SP::element_type*;
|
||||
return inout_ptr_t<SP,ptype,Args...>{res};
|
||||
}
|
||||
else
|
||||
return inout_ptr_t<SP,T,Args...>{res};
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#ifndef AL_NUMERIC_H
|
||||
#define AL_NUMERIC_H
|
||||
|
||||
#include "config_simd.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
@@ -29,19 +31,27 @@ constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_ca
|
||||
constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
|
||||
|
||||
|
||||
constexpr auto GetCounterSuffix(size_t count) noexcept -> const char*
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T>,bool> = true>
|
||||
constexpr auto as_unsigned(T value) noexcept
|
||||
{
|
||||
auto &suffix = (((count%100)/10) == 1) ? "th" :
|
||||
((count%10) == 1) ? "st" :
|
||||
((count%10) == 2) ? "nd" :
|
||||
((count%10) == 3) ? "rd" : "th";
|
||||
return std::data(suffix);
|
||||
using UT = std::make_unsigned_t<T>;
|
||||
return static_cast<UT>(value);
|
||||
}
|
||||
|
||||
|
||||
constexpr inline float lerpf(float val1, float val2, float mu) noexcept
|
||||
constexpr auto GetCounterSuffix(size_t count) noexcept -> std::string_view
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
return (((count%100)/10) == 1) ? "th"sv :
|
||||
((count%10) == 1) ? "st"sv :
|
||||
((count%10) == 2) ? "nd"sv :
|
||||
((count%10) == 3) ? "rd"sv : "th"sv;
|
||||
}
|
||||
|
||||
|
||||
constexpr auto lerpf(float val1, float val2, float mu) noexcept -> float
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
constexpr inline double lerpd(double val1, double val2, double mu) noexcept
|
||||
constexpr auto lerpd(double val1, double val2, double mu) noexcept -> double
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
|
||||
|
||||
@@ -84,7 +94,7 @@ constexpr T RoundUp(T value, al::type_identity_t<T> r) noexcept
|
||||
*/
|
||||
inline int fastf2i(float f) noexcept
|
||||
{
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
return _mm_cvt_ss2si(_mm_set_ss(f));
|
||||
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0
|
||||
@@ -112,7 +122,7 @@ inline unsigned int fastf2u(float f) noexcept
|
||||
/** Converts float-to-int using standard behavior (truncation). */
|
||||
inline int float2int(float f) noexcept
|
||||
{
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
return _mm_cvtt_ss2si(_mm_set_ss(f));
|
||||
|
||||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \
|
||||
@@ -143,7 +153,7 @@ inline unsigned int float2uint(float f) noexcept
|
||||
/** Converts double-to-int using standard behavior (truncation). */
|
||||
inline int double2int(double d) noexcept
|
||||
{
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
return _mm_cvttsd_si32(_mm_set_sd(d));
|
||||
|
||||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \
|
||||
@@ -241,7 +251,7 @@ inline float level_mb_to_gain(float x)
|
||||
// Converts gain to level (mB).
|
||||
inline float gain_to_level_mb(float x)
|
||||
{
|
||||
if (x <= 0.0f)
|
||||
if(x <= 1e-05f)
|
||||
return -10'000.0f;
|
||||
return std::max(std::log10(x) * 2'000.0f, -10'000.0f);
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -39,7 +37,8 @@ semaphore::semaphore(unsigned int initial)
|
||||
{
|
||||
if(initial > static_cast<unsigned int>(std::numeric_limits<int>::max()))
|
||||
throw std::system_error(std::make_error_code(std::errc::value_too_large));
|
||||
mSem = CreateSemaphore(nullptr, initial, std::numeric_limits<int>::max(), nullptr);
|
||||
mSem = CreateSemaphoreW(nullptr, static_cast<LONG>(initial), std::numeric_limits<int>::max(),
|
||||
nullptr);
|
||||
if(mSem == nullptr)
|
||||
throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class semaphore {
|
||||
native_type mSem{};
|
||||
|
||||
public:
|
||||
semaphore(unsigned int initial=0);
|
||||
explicit semaphore(unsigned int initial=0);
|
||||
semaphore(const semaphore&) = delete;
|
||||
~semaphore();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "alassert.h"
|
||||
#include "almalloc.h"
|
||||
#include "altraits.h"
|
||||
|
||||
@@ -141,6 +142,9 @@ namespace detail_ {
|
||||
|
||||
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
|
||||
|
||||
/* NOLINTBEGIN(google-explicit-constructor) This largely follows std::span's
|
||||
* constructor behavior, and should be replaced once C++20 is used.
|
||||
*/
|
||||
template<typename T, std::size_t E>
|
||||
class span {
|
||||
public:
|
||||
@@ -164,12 +168,11 @@ public:
|
||||
template<bool is0=(extent == 0), REQUIRES(is0)>
|
||||
constexpr span() noexcept { }
|
||||
template<typename U>
|
||||
constexpr explicit span(U iter, size_type size_ [[maybe_unused]])
|
||||
: mData{::al::to_address(iter)}
|
||||
{ assert(size_ == extent); }
|
||||
constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)}
|
||||
{ alassert(size_ == extent); }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
|
||||
constexpr explicit span(U first, V last [[maybe_unused]]) : mData{::al::to_address(first)}
|
||||
{ assert(static_cast<std::size_t>(last-first) == extent); }
|
||||
constexpr explicit span(U first, V last) : mData{::al::to_address(first)}
|
||||
{ alassert(static_cast<std::size_t>(last-first) == extent); }
|
||||
|
||||
template<std::size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
|
||||
@@ -188,7 +191,7 @@ public:
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == dynamic_extent)>
|
||||
constexpr explicit span(const span<U,N> &span_) noexcept : mData{std::data(span_)}
|
||||
{ assert(std::size(span_) == extent); }
|
||||
{ alassert(std::size(span_) == extent); }
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == extent)>
|
||||
constexpr span(const span<U,N> &span_) noexcept : mData{std::data(span_)} { }
|
||||
@@ -212,22 +215,24 @@ public:
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; }
|
||||
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const -> span<element_type,C>
|
||||
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const -> span<element_type,C>
|
||||
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData+(E-C), C};
|
||||
@@ -235,7 +240,7 @@ public:
|
||||
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
static_assert(E-O >= C, "New size exceeds original capacity");
|
||||
@@ -244,7 +249,7 @@ public:
|
||||
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
return span<element_type,E-O>{mData+O, E-O};
|
||||
@@ -254,12 +259,13 @@ public:
|
||||
* defining the specialization. As a result, these methods need to be
|
||||
* defined later.
|
||||
*/
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const
|
||||
[[nodiscard]] constexpr
|
||||
auto first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr
|
||||
auto last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept
|
||||
-> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const
|
||||
-> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr auto subspan(std::size_t offset,
|
||||
std::size_t count=dynamic_extent) const -> span<element_type,dynamic_extent>;
|
||||
|
||||
private:
|
||||
pointer mData{nullptr};
|
||||
@@ -335,72 +341,65 @@ public:
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; }
|
||||
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const -> span<element_type,C>
|
||||
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
|
||||
{
|
||||
if(C > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(C <= mDataLength);
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const -> span
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span
|
||||
{
|
||||
if(count > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(count <= mDataLength);
|
||||
return span{mData, count};
|
||||
}
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const -> span<element_type,C>
|
||||
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
|
||||
{
|
||||
if(C > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(C <= mDataLength);
|
||||
return span<element_type,C>{mData+mDataLength-C, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const -> span
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span
|
||||
{
|
||||
if(count > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(count <= mDataLength);
|
||||
return span{mData+mDataLength-count, count};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
if(O > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
if(C > mDataLength-O)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
assert(O <= mDataLength);
|
||||
assert(C <= mDataLength-O);
|
||||
return span<element_type,C>{mData+O, C};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
if(O > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
assert(O <= mDataLength);
|
||||
return span<element_type,C>{mData+O, mDataLength-O};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const -> span
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span
|
||||
{
|
||||
if(offset > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
assert(offset <= mDataLength);
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
if(count > mDataLength-offset)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
assert(count <= mDataLength-offset);
|
||||
return span{mData+offset, count};
|
||||
}
|
||||
return span{mData+offset, mDataLength-offset};
|
||||
@@ -413,38 +412,34 @@ private:
|
||||
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::first(std::size_t count) const -> span<element_type,dynamic_extent>
|
||||
auto span<T,E>::first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
|
||||
{
|
||||
if(count > size())
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(count <= size());
|
||||
return span<element_type>{mData, count};
|
||||
}
|
||||
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::last(std::size_t count) const -> span<element_type,dynamic_extent>
|
||||
auto span<T,E>::last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
|
||||
{
|
||||
if(count > size())
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
assert(count <= size());
|
||||
return span<element_type>{mData+size()-count, count};
|
||||
}
|
||||
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::subspan(std::size_t offset, std::size_t count) const
|
||||
auto span<T,E>::subspan(std::size_t offset, std::size_t count) const noexcept
|
||||
-> span<element_type,dynamic_extent>
|
||||
{
|
||||
if(offset > size())
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
assert(offset <= size());
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
if(count > size()-offset)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
assert(count <= size()-offset);
|
||||
return span<element_type>{mData+offset, count};
|
||||
}
|
||||
return span<element_type>{mData+offset, size()-offset};
|
||||
}
|
||||
|
||||
/* NOLINTEND(google-explicit-constructor) */
|
||||
|
||||
template<typename T, typename EndOrSize>
|
||||
span(T, EndOrSize) -> span<std::remove_reference_t<decltype(*std::declval<T&>())>>;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace al {
|
||||
@@ -31,13 +31,24 @@ int case_compare(const std::string_view str0, const std::string_view str1) noexc
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept
|
||||
{ return case_compare(str0, str1); }
|
||||
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept
|
||||
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept
|
||||
{
|
||||
return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)},
|
||||
std::string_view{str1, std::min(std::strlen(str1), len)});
|
||||
using Traits = std::wstring_view::traits_type;
|
||||
|
||||
auto ch0 = str0.cbegin();
|
||||
auto ch1 = str1.cbegin();
|
||||
auto ch1end = ch1 + std::min(str0.size(), str1.size());
|
||||
while(ch1 != ch1end)
|
||||
{
|
||||
const auto u0 = std::towupper(Traits::to_int_type(*ch0));
|
||||
const auto u1 = std::towupper(Traits::to_int_type(*ch1));
|
||||
if(const auto diff = static_cast<int>(u0-u1)) return diff;
|
||||
++ch0; ++ch1;
|
||||
}
|
||||
|
||||
if(str0.size() < str1.size()) return -1;
|
||||
if(str0.size() > str1.size()) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -10,11 +10,17 @@
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, typename Traits>
|
||||
template<typename ...Ts>
|
||||
[[nodiscard]] constexpr
|
||||
auto sizei(const std::basic_string_view<T,Traits> str) noexcept -> int
|
||||
auto sizei(const std::basic_string_view<Ts...> str) noexcept -> int
|
||||
{ return static_cast<int>(std::min<std::size_t>(str.size(), std::numeric_limits<int>::max())); }
|
||||
|
||||
template<typename ...Ts>
|
||||
[[nodiscard]] constexpr
|
||||
auto sizei(const std::basic_string<Ts...> &str) noexcept -> int
|
||||
{ return static_cast<int>(std::min<std::size_t>(str.size(), std::numeric_limits<int>::max())); }
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.find(str1) != std::string_view::npos; }
|
||||
@@ -31,9 +37,22 @@ constexpr bool ends_with(const std::string_view str0, const std::string_view str
|
||||
int case_compare(const std::string_view str0, const std::string_view str1) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept;
|
||||
[[nodiscard]]
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept;
|
||||
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept;
|
||||
|
||||
/* C++20 changes path::u8string() to return a string using a new/distinct
|
||||
* char8_t type for UTF-8 strings. However, support for this with standard
|
||||
* string functions is totally inadequate, and we already hold UTF-8 with plain
|
||||
* char strings. So this function is used to reinterpret a char8_t string as a
|
||||
* char string_view.
|
||||
*/
|
||||
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L
|
||||
inline auto u8_as_char(const std::u8string_view str) -> std::string_view
|
||||
#else
|
||||
inline auto u8_as_char(const std::string_view str) -> std::string_view
|
||||
#endif
|
||||
{
|
||||
return std::string_view{reinterpret_cast<const char*>(str.data()), str.size()};
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__STDC_NO_THREADS__) || !__has_include(<threads.h>)
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -55,7 +55,7 @@ class tss {
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD mTss;
|
||||
DWORD mTss{TLS_OUT_OF_INDEXES};
|
||||
|
||||
public:
|
||||
tss() : mTss{TlsAlloc()}
|
||||
@@ -79,9 +79,9 @@ public:
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); }
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__STDC_NO_THREADS__) || !__has_include(<threads.h>)
|
||||
|
||||
pthread_key_t mTss;
|
||||
pthread_key_t mTss{};
|
||||
|
||||
public:
|
||||
tss()
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
|
||||
#else
|
||||
|
||||
tss_t mTss;
|
||||
tss_t mTss{};
|
||||
|
||||
public:
|
||||
tss()
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#ifndef COMMON_COMPTR_H
|
||||
#define COMMON_COMPTR_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
@@ -17,7 +15,7 @@ struct ComWrapper {
|
||||
ComWrapper(void *reserved, DWORD coinit)
|
||||
: mStatus{CoInitializeEx(reserved, coinit)}
|
||||
{ }
|
||||
ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED)
|
||||
explicit ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED)
|
||||
: mStatus{CoInitializeEx(nullptr, coinit)}
|
||||
{ }
|
||||
ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); }
|
||||
@@ -33,6 +31,7 @@ struct ComWrapper {
|
||||
}
|
||||
ComWrapper& operator=(const ComWrapper&) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
HRESULT status() const noexcept { return mStatus; }
|
||||
explicit operator bool() const noexcept { return SUCCEEDED(status()); }
|
||||
|
||||
@@ -45,7 +44,7 @@ struct ComWrapper {
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
template<typename T> /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */
|
||||
struct ComPtr {
|
||||
using element_type = T;
|
||||
|
||||
@@ -56,10 +55,11 @@ struct ComPtr {
|
||||
ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr}
|
||||
{ if(mPtr) mPtr->AddRef(); }
|
||||
ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
|
||||
ComPtr(std::nullptr_t) noexcept { }
|
||||
ComPtr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */
|
||||
explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~ComPtr() { if(mPtr) mPtr->Release(); }
|
||||
|
||||
/* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */
|
||||
ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if constexpr(RefIsNoexcept)
|
||||
@@ -107,5 +107,6 @@ struct ComPtr {
|
||||
private:
|
||||
T *mPtr{nullptr};
|
||||
};
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
|
||||
#if defined(_WIN32) || defined(HAVE_DLFCN_H)
|
||||
|
||||
#define HAVE_DYNLOAD
|
||||
#define HAVE_DYNLOAD 1
|
||||
|
||||
void *LoadLib(const char *name);
|
||||
void CloseLib(void *handle);
|
||||
void *GetSymbol(void *handle, const char *name);
|
||||
|
||||
#else
|
||||
|
||||
#define HAVE_DYNLOAD 0
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* AL_DYNLOAD_H */
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//---------------------------------------------------------------------------------------
|
||||
//
|
||||
// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
// fs_std_impl.hpp - The implementation header for the header/implementation separated usage of
|
||||
// ghc::filesystem that does nothing if std::filesystem is detected.
|
||||
// This file can be used to hide the implementation of ghc::filesystem into a single cpp.
|
||||
// The cpp has to include this before including fs_std_fwd.hpp directly or via a different
|
||||
// header to work.
|
||||
//---------------------------------------------------------------------------------------
|
||||
#if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include)
|
||||
// ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus
|
||||
// _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/
|
||||
#if __has_include(<filesystem>) // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html
|
||||
#define GHC_USE_STD_FS
|
||||
|
||||
// Old Apple OSs don't support std::filesystem, though the header is available at compile
|
||||
// time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0,
|
||||
// and watchOS 6.0.
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h>
|
||||
// Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS
|
||||
// released after std::filesystem, where std::filesystem is always available.
|
||||
// (All other __<platform>_VERSION_MIN_REQUIREDs will be undefined and thus 0.)
|
||||
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \
|
||||
|| defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \
|
||||
|| defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \
|
||||
|| defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000
|
||||
#undef GHC_USE_STD_FS
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef GHC_USE_STD_FS
|
||||
#define GHC_FILESYSTEM_IMPLEMENTATION
|
||||
#include "ghc_filesystem.h"
|
||||
#endif
|
||||
@@ -0,0 +1,81 @@
|
||||
//---------------------------------------------------------------------------------------
|
||||
//
|
||||
// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
// fs_std_fwd.hpp - The forwarding header for the header/implementation separated usage of
|
||||
// ghc::filesystem that uses std::filesystem if it detects it.
|
||||
// This file can be include at any place, where fs::filesystem api is needed while
|
||||
// not bleeding implementation details (e.g. system includes) into the global namespace,
|
||||
// as long as one cpp includes fs_std_impl.hpp to deliver the matching implementations.
|
||||
//---------------------------------------------------------------------------------------
|
||||
#ifndef GHC_FILESYSTEM_STD_FWD_H
|
||||
#define GHC_FILESYSTEM_STD_FWD_H
|
||||
|
||||
#if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include)
|
||||
// ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus
|
||||
// _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/
|
||||
#if __has_include(<filesystem>) // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html
|
||||
#define GHC_USE_STD_FS
|
||||
|
||||
// Old Apple OSs don't support std::filesystem, though the header is available at compile
|
||||
// time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0,
|
||||
// and watchOS 6.0.
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h>
|
||||
// Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS
|
||||
// released after std::filesystem, where std::filesystem is always available.
|
||||
// (All other __<platform>_VERSION_MIN_REQUIREDs will be undefined and thus 0.)
|
||||
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \
|
||||
|| defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \
|
||||
|| defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \
|
||||
|| defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000
|
||||
#undef GHC_USE_STD_FS
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef GHC_USE_STD_FS
|
||||
#include <filesystem>
|
||||
namespace fs {
|
||||
using namespace std::filesystem;
|
||||
using ifstream = std::ifstream;
|
||||
using ofstream = std::ofstream;
|
||||
using fstream = std::fstream;
|
||||
}
|
||||
#else
|
||||
#define GHC_FILESYSTEM_FWD
|
||||
#include "ghc_filesystem.h"
|
||||
|
||||
namespace fs {
|
||||
using namespace ghc::filesystem;
|
||||
using ifstream = ghc::filesystem::ifstream;
|
||||
using ofstream = ghc::filesystem::ofstream;
|
||||
using fstream = ghc::filesystem::fstream;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // GHC_FILESYSTEM_STD_FWD_H
|
||||
@@ -17,7 +17,7 @@ namespace al {
|
||||
* trivially destructible.
|
||||
*/
|
||||
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
|
||||
struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage : al::span<T> {
|
||||
struct alignas(alignment) FlexArrayStorage : al::span<T> {
|
||||
/* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the
|
||||
* sizeof(T) being suspicious when T is a pointer type, which it will be
|
||||
* for flexible arrays of pointers.
|
||||
@@ -30,7 +30,7 @@ struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage : al:
|
||||
* arrays store their payloads after the end of the object, which must be
|
||||
* the last in the whole parent chain.
|
||||
*/
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
@@ -41,12 +41,12 @@ struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage : al:
|
||||
};
|
||||
|
||||
template<typename T, size_t alignment>
|
||||
struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage<T,alignment,false> : al::span<T> {
|
||||
struct alignas(alignment) FlexArrayStorage<T,alignment,false> : al::span<T> {
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
@@ -73,7 +73,8 @@ struct FlexArray {
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using Storage_t_ = FlexArrayStorage<element_type, std::max(alignof(T), Align)>;
|
||||
static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)};
|
||||
using Storage_t_ = FlexArrayStorage<element_type,std::max(alignof(al::span<T>), StorageAlign)>;
|
||||
|
||||
using iterator = typename Storage_t_::iterator;
|
||||
using const_iterator = typename Storage_t_::const_iterator;
|
||||
@@ -87,7 +88,8 @@ struct FlexArray {
|
||||
static std::unique_ptr<FlexArray> Create(index_type count)
|
||||
{ return std::unique_ptr<FlexArray>{new(FamCount{count}) FlexArray{count}}; }
|
||||
|
||||
FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v<Storage_t_,index_type>)
|
||||
explicit FlexArray(index_type size)
|
||||
noexcept(std::is_nothrow_constructible_v<Storage_t_,index_type>)
|
||||
: mStore{size}
|
||||
{ }
|
||||
~FlexArray() = default;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,9 @@ template<typename T>
|
||||
class intrusive_ref {
|
||||
std::atomic<unsigned int> mRef{1u};
|
||||
|
||||
protected:
|
||||
~intrusive_ref() = default;
|
||||
|
||||
public:
|
||||
unsigned int add_ref() noexcept { return IncrementRef(mRef); }
|
||||
unsigned int dec_ref() noexcept
|
||||
@@ -48,7 +51,7 @@ public:
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
template<typename T> /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */
|
||||
class intrusive_ptr {
|
||||
T *mPtr{nullptr};
|
||||
|
||||
@@ -58,7 +61,7 @@ public:
|
||||
{ if(mPtr) mPtr->add_ref(); }
|
||||
intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr}
|
||||
{ rhs.mPtr = nullptr; }
|
||||
intrusive_ptr(std::nullptr_t) noexcept { }
|
||||
intrusive_ptr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */
|
||||
explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); }
|
||||
|
||||
|
||||
@@ -28,6 +28,24 @@
|
||||
#define NOINLINE
|
||||
#endif
|
||||
|
||||
#if defined(__MINGW32__) && defined(__i386__)
|
||||
/* 32-bit MinGW targets have a bug where __STDCPP_DEFAULT_NEW_ALIGNMENT__
|
||||
* reports 16, despite the default operator new calling standard malloc which
|
||||
* only guarantees 8-byte alignment. As a result, structs that need and specify
|
||||
* 16-byte alignment only get 8-byte alignment. Explicitly specifying 32-byte
|
||||
* alignment forces the over-aligned operator new to be called, giving the
|
||||
* correct (if larger than necessary) alignment.
|
||||
*
|
||||
* Technically this bug affects 32-bit GCC more generally, but typically only
|
||||
* with fairly old glibc versions as newer versions do guarantee the 16-byte
|
||||
* alignment as specified. MinGW is reliant on msvcrt.dll's malloc however,
|
||||
* which can't be updated to give that guarantee.
|
||||
*/
|
||||
#define SIMDALIGN alignas(32)
|
||||
#else
|
||||
#define SIMDALIGN
|
||||
#endif
|
||||
|
||||
/* Unlike the likely attribute, ASSUME requires the condition to be true or
|
||||
* else it invokes undefined behavior. It's essentially an assert without
|
||||
* actually checking the condition at run-time, allowing for stronger
|
||||
@@ -56,6 +74,12 @@
|
||||
#define UNLIKELY
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && HAS_ATTRIBUTE(gnu::visibility)
|
||||
#define DECL_HIDDEN [[gnu::visibility("hidden")]]
|
||||
#else
|
||||
#define DECL_HIDDEN
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -73,6 +73,8 @@
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "fmt/core.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
@@ -80,6 +82,12 @@ using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(__GNUC__) || defined(_MSC_VER)
|
||||
#define RESTRICT __restrict
|
||||
#else
|
||||
#define RESTRICT
|
||||
#endif
|
||||
|
||||
/* Vector support macros: the rest of the code is independent of
|
||||
* SSE/Altivec/NEON -- adding support for other platforms with 4-element
|
||||
* vectors should be limited to these macros
|
||||
@@ -92,7 +100,8 @@ namespace {
|
||||
/*
|
||||
* Altivec support macros
|
||||
*/
|
||||
#if defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__)
|
||||
#if (defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__)) \
|
||||
&& (defined(__VEC__) || defined(__ALTIVEC__))
|
||||
#include <altivec.h>
|
||||
using v4sf = vector float;
|
||||
constexpr uint SimdSize{4};
|
||||
@@ -114,15 +123,13 @@ force_inline float vextract0(v4sf v) noexcept { return vec_extract(v, 0); }
|
||||
|
||||
force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{vec_mergeh(in1, in2)};
|
||||
out1 = vec_mergeh(in1, in2);
|
||||
out2 = vec_mergel(in1, in2);
|
||||
out1 = tmp;
|
||||
}
|
||||
force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27})};
|
||||
out1 = vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27});
|
||||
out2 = vec_perm(in1, in2, (vector unsigned char){4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31});
|
||||
out1 = tmp;
|
||||
}
|
||||
|
||||
force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept
|
||||
@@ -169,15 +176,13 @@ force_inline float vextract0(v4sf v) noexcept
|
||||
|
||||
force_inline void interleave2(const v4sf in1, const v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{_mm_unpacklo_ps(in1, in2)};
|
||||
out1 = _mm_unpacklo_ps(in1, in2);
|
||||
out2 = _mm_unpackhi_ps(in1, in2);
|
||||
out1 = tmp;
|
||||
}
|
||||
force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{_mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0))};
|
||||
out1 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0));
|
||||
out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1));
|
||||
out1 = tmp;
|
||||
}
|
||||
|
||||
force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept
|
||||
@@ -277,15 +282,13 @@ force_inline v4sf unpackhi(v4sf a, v4sf b) noexcept
|
||||
|
||||
force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{unpacklo(in1, in2)};
|
||||
out1 = unpacklo(in1, in2);
|
||||
out2 = unpackhi(in1, in2);
|
||||
out1 = tmp;
|
||||
}
|
||||
force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept
|
||||
{
|
||||
v4sf tmp{in1[0], in1[2], in2[0], in2[2]};
|
||||
out1 = v4sf{in1[0], in1[2], in2[0], in2[2]};
|
||||
out2 = v4sf{in1[1], in1[3], in2[1], in2[3]};
|
||||
out1 = tmp;
|
||||
}
|
||||
|
||||
force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept
|
||||
@@ -323,7 +326,7 @@ force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; }
|
||||
|
||||
#else
|
||||
|
||||
[[maybe_unused]] inline
|
||||
[[maybe_unused, nodiscard]] inline
|
||||
auto valigned(const float *ptr) noexcept -> bool
|
||||
{
|
||||
static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1};
|
||||
@@ -357,61 +360,59 @@ constexpr auto make_float_array(std::integer_sequence<T,N...>)
|
||||
{ return std::array{static_cast<float>(N)...}; }
|
||||
|
||||
/* detect bugs with the vector support macros */
|
||||
[[maybe_unused]] void validate_pffft_simd()
|
||||
[[maybe_unused]] auto validate_pffft_simd() -> bool
|
||||
{
|
||||
using float4 = std::array<float,4>;
|
||||
static constexpr auto f = make_float_array(std::make_index_sequence<16>{});
|
||||
|
||||
v4sf a0_v{vset4(f[ 0], f[ 1], f[ 2], f[ 3])};
|
||||
v4sf a1_v{vset4(f[ 4], f[ 5], f[ 6], f[ 7])};
|
||||
v4sf a2_v{vset4(f[ 8], f[ 9], f[10], f[11])};
|
||||
v4sf a3_v{vset4(f[12], f[13], f[14], f[15])};
|
||||
v4sf u_v{};
|
||||
auto a0_v = vset4(f[ 0], f[ 1], f[ 2], f[ 3]);
|
||||
auto a1_v = vset4(f[ 4], f[ 5], f[ 6], f[ 7]);
|
||||
auto a2_v = vset4(f[ 8], f[ 9], f[10], f[11]);
|
||||
auto a3_v = vset4(f[12], f[13], f[14], f[15]);
|
||||
|
||||
auto t_v = vzero();
|
||||
auto t_f = al::bit_cast<float4>(t_v);
|
||||
printf("VZERO=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("VZERO={}", t_f);
|
||||
assertv4(t_f, 0, 0, 0, 0);
|
||||
|
||||
t_v = vadd(a1_v, a2_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("VADD(4:7,8:11)={}", t_f);
|
||||
assertv4(t_f, 12, 14, 16, 18);
|
||||
|
||||
t_v = vmul(a1_v, a2_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("VMUL(4:7,8:11)={}", t_f);
|
||||
assertv4(t_f, 32, 45, 60, 77);
|
||||
|
||||
t_v = vmadd(a1_v, a2_v, a0_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("VMADD(4:7,8:11,0:3)={}", t_f);
|
||||
assertv4(t_f, 32, 46, 62, 80);
|
||||
|
||||
auto u_v = v4sf{};
|
||||
interleave2(a1_v, a2_v, t_v, u_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
auto u_f = al::bit_cast<float4>(u_v);
|
||||
printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
|
||||
t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]);
|
||||
fmt::println("INTERLEAVE2(4:7,8:11)={} {}", t_f, u_f);
|
||||
assertv4(t_f, 4, 8, 5, 9);
|
||||
assertv4(u_f, 6, 10, 7, 11);
|
||||
|
||||
uninterleave2(a1_v, a2_v, t_v, u_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
u_f = al::bit_cast<float4>(u_v);
|
||||
printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
|
||||
t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]);
|
||||
fmt::println("UNINTERLEAVE2(4:7,8:11)={} {}", t_f, u_f);
|
||||
assertv4(t_f, 4, 6, 8, 10);
|
||||
assertv4(u_f, 5, 7, 9, 11);
|
||||
|
||||
t_v = ld_ps1(f[15]);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("LD_PS1(15)={}", t_f);
|
||||
assertv4(t_f, 15, 15, 15, 15);
|
||||
|
||||
t_v = vswaphl(a1_v, a2_v);
|
||||
t_f = al::bit_cast<float4>(t_v);
|
||||
printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]);
|
||||
fmt::println("VSWAPHL(4:7,8:11)={}", t_f);
|
||||
assertv4(t_f, 8, 9, 6, 7);
|
||||
|
||||
vtranspose4(a0_v, a1_v, a2_v, a3_v);
|
||||
@@ -419,13 +420,13 @@ constexpr auto make_float_array(std::integer_sequence<T,N...>)
|
||||
auto a1_f = al::bit_cast<float4>(a1_v);
|
||||
auto a2_f = al::bit_cast<float4>(a2_v);
|
||||
auto a3_f = al::bit_cast<float4>(a3_v);
|
||||
printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
|
||||
a0_f[0], a0_f[1], a0_f[2], a0_f[3], a1_f[0], a1_f[1], a1_f[2], a1_f[3],
|
||||
a2_f[0], a2_f[1], a2_f[2], a2_f[3], a3_f[0], a3_f[1], a3_f[2], a3_f[3]);
|
||||
fmt::println("VTRANSPOSE4(0:3,4:7,8:11,12:15)={} {} {} {}", a0_f, a1_f, a2_f, a3_f);
|
||||
assertv4(a0_f, 0, 4, 8, 12);
|
||||
assertv4(a1_f, 1, 5, 9, 13);
|
||||
assertv4(a2_f, 2, 6, 10, 14);
|
||||
assertv4(a3_f, 3, 7, 11, 15);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif //!PFFFT_SIMD_DISABLE
|
||||
|
||||
@@ -434,6 +435,13 @@ constexpr auto make_float_array(std::integer_sequence<T,N...>)
|
||||
constexpr auto V4sfAlignment = size_t(64);
|
||||
constexpr auto V4sfAlignVal = std::align_val_t(V4sfAlignment);
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
* FIXME: Converting this from raw pointers to spans or something will probably
|
||||
* need significant work to maintain performance, given non-sequential range-
|
||||
* checked accesses and lack of 'restrict' to indicate non-aliased memory. At
|
||||
* least, some tests should be done to check the impact of using range-checked
|
||||
* spans here before blindly switching.
|
||||
*/
|
||||
/*
|
||||
passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2
|
||||
*/
|
||||
@@ -1051,7 +1059,7 @@ void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *
|
||||
ch_ref(1, 3, k) = vmadd(ti11, ci5, vmul(ti12, ci4));
|
||||
ch_ref(ido, 4, k) = vadd(cc_ref(1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3)));
|
||||
ch_ref(1, 5, k) = vsub(vmul(ti12, ci5), vmul(ti11, ci4));
|
||||
//printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4);
|
||||
//fmt::println("pffft: radf5, k={} ch_ref={:f}, ci4={:f}", k, ch_ref(1, 5, k), ci4);
|
||||
}
|
||||
if(ido == 1)
|
||||
return;
|
||||
@@ -1526,10 +1534,10 @@ PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform)
|
||||
}
|
||||
|
||||
|
||||
void pffft_destroy_setup(gsl::owner<PFFFT_Setup*> s) noexcept
|
||||
void PFFFTSetupDeleter::operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept
|
||||
{
|
||||
std::destroy_at(s);
|
||||
::operator delete[](gsl::owner<void*>{s}, V4sfAlignVal);
|
||||
std::destroy_at(setup);
|
||||
::operator delete[](gsl::owner<void*>{setup}, V4sfAlignVal);
|
||||
}
|
||||
|
||||
#if !defined(PFFFT_SIMD_DISABLE)
|
||||
@@ -1810,7 +1818,7 @@ NOINLINE void pffft_real_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RE
|
||||
const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks
|
||||
/* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */
|
||||
|
||||
std::array<float,SimdSize> Xr, Xi;
|
||||
std::array<float,SimdSize> Xr{}, Xi{};
|
||||
for(size_t k{0};k < SimdSize;++k)
|
||||
{
|
||||
Xr[k] = vextract0(in[2*k]);
|
||||
@@ -2296,4 +2304,5 @@ void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float
|
||||
pffft_transform_internal(setup, input, output, work, direction, true);
|
||||
}
|
||||
|
||||
#endif // defined(PFFFT_SIMD_DISABLE)
|
||||
#endif /* defined(PFFFT_SIMD_DISABLE) */
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
|
||||
@@ -96,9 +96,8 @@ enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD };
|
||||
/* type of transform */
|
||||
enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX };
|
||||
|
||||
void pffft_destroy_setup(gsl::owner<PFFFT_Setup*> setup) noexcept;
|
||||
struct PFFFTSetupDeleter {
|
||||
void operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept { pffft_destroy_setup(setup); }
|
||||
void operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept;
|
||||
};
|
||||
using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;
|
||||
|
||||
@@ -175,7 +174,7 @@ void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, co
|
||||
|
||||
|
||||
struct PFFFTSetup {
|
||||
PFFFTSetupPtr mSetup{};
|
||||
PFFFTSetupPtr mSetup;
|
||||
|
||||
PFFFTSetup() = default;
|
||||
PFFFTSetup(const PFFFTSetup&) = delete;
|
||||
@@ -189,6 +188,8 @@ struct PFFFTSetup {
|
||||
PFFFTSetup& operator=(const PFFFTSetup&) = delete;
|
||||
PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; }
|
||||
|
||||
void transform(const float *input, float *output, float *work, pffft_direction_t direction) const
|
||||
{ pffft_transform(mSetup.get(), input, output, work, direction); }
|
||||
|
||||
@@ -205,8 +206,6 @@ struct PFFFTSetup {
|
||||
|
||||
void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const
|
||||
{ pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); }
|
||||
|
||||
[[nodiscard]] operator bool() const noexcept { return mSetup != nullptr; }
|
||||
};
|
||||
|
||||
#endif // PFFFT_H
|
||||
|
||||
@@ -1,95 +1,59 @@
|
||||
#ifndef PHASE_SHIFTER_H
|
||||
#define PHASE_SHIFTER_H
|
||||
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include "config_simd.h"
|
||||
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(HAVE_NEON)
|
||||
#elif HAVE_NEON
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
struct NoInit { };
|
||||
|
||||
/* Implements a wide-band +90 degree phase-shift. Note that this should be
|
||||
* given one sample less of a delay (FilterSize/2 - 1) compared to the direct
|
||||
* signal delay (FilterSize/2) to properly align.
|
||||
*/
|
||||
template<std::size_t FilterSize>
|
||||
struct PhaseShifterT {
|
||||
struct SIMDALIGN PhaseShifterT {
|
||||
static_assert(FilterSize >= 16, "FilterSize needs to be at least 16");
|
||||
static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two");
|
||||
|
||||
alignas(16) std::array<float,FilterSize/2> mCoeffs{};
|
||||
|
||||
/* Some notes on this filter construction.
|
||||
*
|
||||
* A wide-band phase-shift filter needs a delay to maintain linearity. A
|
||||
* dirac impulse in the center of a time-domain buffer represents a filter
|
||||
* passing all frequencies through as-is with a pure delay. Converting that
|
||||
* to the frequency domain, adjusting the phase of each frequency bin by
|
||||
* +90 degrees, then converting back to the time domain, results in a FIR
|
||||
* filter that applies a +90 degree wide-band phase-shift.
|
||||
*
|
||||
* A particularly notable aspect of the time-domain filter response is that
|
||||
* every other coefficient is 0. This allows doubling the effective size of
|
||||
* the filter, by storing only the non-0 coefficients and double-stepping
|
||||
* over the input to apply it.
|
||||
*
|
||||
* Additionally, the resulting filter is independent of the sample rate.
|
||||
* The same filter can be applied regardless of the device's sample rate
|
||||
* and achieve the same effect.
|
||||
*/
|
||||
PhaseShifterT()
|
||||
{
|
||||
using complex_d = std::complex<double>;
|
||||
constexpr std::size_t fft_size{FilterSize};
|
||||
constexpr std::size_t half_size{fft_size / 2};
|
||||
|
||||
auto fftBuffer = std::vector<complex_d>(fft_size, complex_d{});
|
||||
fftBuffer[half_size] = 1.0;
|
||||
|
||||
forward_fft(al::span{fftBuffer});
|
||||
fftBuffer[0] *= std::numeric_limits<double>::epsilon();
|
||||
for(std::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(std::size_t i{half_size+1};i < fft_size;++i)
|
||||
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
|
||||
inverse_fft(al::span{fftBuffer});
|
||||
|
||||
auto fftiter = fftBuffer.data() + fft_size - 1;
|
||||
for(float &coeff : mCoeffs)
|
||||
/* Every other coefficient is 0, so we only need to calculate and store
|
||||
* the non-0 terms and double-step over the input to apply it. The
|
||||
* calculated coefficients are in reverse to make applying in the time-
|
||||
* domain more efficient.
|
||||
*/
|
||||
for(std::size_t i{0};i < FilterSize/2;++i)
|
||||
{
|
||||
coeff = static_cast<float>(fftiter->real() / double{fft_size});
|
||||
fftiter -= 2;
|
||||
const auto k = static_cast<int>(i*2 + 1) - int{FilterSize/2};
|
||||
|
||||
/* Calculate the Blackman window value for this coefficient. */
|
||||
const auto w = 2.0*al::numbers::pi/double{FilterSize} * 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);
|
||||
mCoeffs[i] = static_cast<float>(window * (1.0-std::cos(pk)) / pk);
|
||||
}
|
||||
}
|
||||
|
||||
PhaseShifterT(NoInit) { }
|
||||
|
||||
void process(al::span<float> dst, const float *RESTRICT src) const;
|
||||
void process(const al::span<float> dst, const al::span<const float> src) const;
|
||||
|
||||
private:
|
||||
#if defined(HAVE_NEON)
|
||||
static auto unpacklo(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))};
|
||||
return vcombine_f32(result.val[0], result.val[1]);
|
||||
}
|
||||
static auto unpackhi(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x2x2_t result{vzip_f32(vget_high_f32(a), vget_high_f32(b))};
|
||||
return vcombine_f32(result.val[0], result.val[1]);
|
||||
}
|
||||
#if HAVE_NEON
|
||||
static auto load4(float32_t a, float32_t b, float32_t c, float32_t d)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(a)};
|
||||
@@ -98,106 +62,141 @@ private:
|
||||
ret = vsetq_lane_f32(d, ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3)
|
||||
{
|
||||
float32x4x2_t t0_{vzipq_f32(x0, x2)};
|
||||
float32x4x2_t t1_{vzipq_f32(x1, x3)};
|
||||
float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])};
|
||||
float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])};
|
||||
x0 = u0_.val[0];
|
||||
x1 = u0_.val[1];
|
||||
x2 = u1_.val[0];
|
||||
x3 = u1_.val[1];
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template<std::size_t S>
|
||||
inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT src) const
|
||||
NOINLINE inline
|
||||
void PhaseShifterT<S>::process(const al::span<float> dst, const al::span<const float> src) const
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
if(std::size_t todo{dst.size()>>1})
|
||||
auto in = src.begin();
|
||||
#if HAVE_SSE_INTRINSICS
|
||||
if(const std::size_t todo{dst.size()>>2})
|
||||
{
|
||||
auto *out = reinterpret_cast<__m64*>(dst.data());
|
||||
do {
|
||||
__m128 r04{_mm_setzero_ps()};
|
||||
__m128 r14{_mm_setzero_ps()};
|
||||
auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo};
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
__m128 r0{_mm_setzero_ps()};
|
||||
__m128 r1{_mm_setzero_ps()};
|
||||
__m128 r2{_mm_setzero_ps()};
|
||||
__m128 r3{_mm_setzero_ps()};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s0{_mm_loadu_ps(&src[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])};
|
||||
const __m128 s0{_mm_loadu_ps(&in[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])};
|
||||
const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)};
|
||||
const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1),
|
||||
reinterpret_cast<const __m64*>(&in[j*2 + 8]))};
|
||||
|
||||
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
|
||||
r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs));
|
||||
r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs));
|
||||
r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
src += 2;
|
||||
in += 4;
|
||||
|
||||
__m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))};
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
|
||||
_mm_storel_pi(out, r4);
|
||||
++out;
|
||||
} while(--todo);
|
||||
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
|
||||
return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3));
|
||||
});
|
||||
}
|
||||
if((dst.size()&1))
|
||||
if(const std::size_t todo{dst.size()&3})
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
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));
|
||||
|
||||
dst.back() = _mm_cvtss_f32(r4);
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
++in;
|
||||
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));
|
||||
return _mm_cvtss_f32(r4);
|
||||
});
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
#elif HAVE_NEON
|
||||
|
||||
std::size_t pos{0};
|
||||
if(std::size_t todo{dst.size()>>1})
|
||||
if(const std::size_t todo{dst.size()>>2})
|
||||
{
|
||||
do {
|
||||
float32x4_t r04{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r14{vdupq_n_f32(0.0f)};
|
||||
auto out = al::span{reinterpret_cast<float32x4_t*>(dst.data()), todo};
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
float32x4_t r0{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r1{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r2{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r3{vdupq_n_f32(0.0f)};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s0{vld1q_f32(&src[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
|
||||
const float32x4x2_t values{vuzpq_f32(s0, s1)};
|
||||
const float32x4_t s0{vld1q_f32(&in[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&in[j*2 + 4])};
|
||||
const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))};
|
||||
const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))};
|
||||
const float32x4x2_t values0{vuzpq_f32(s0, s1)};
|
||||
const float32x4x2_t values1{vuzpq_f32(s2, s3)};
|
||||
|
||||
r04 = vmlaq_f32(r04, values.val[0], coeffs);
|
||||
r14 = vmlaq_f32(r14, values.val[1], coeffs);
|
||||
r0 = vmlaq_f32(r0, values0.val[0], coeffs);
|
||||
r1 = vmlaq_f32(r1, values0.val[1], coeffs);
|
||||
r2 = vmlaq_f32(r2, values1.val[0], coeffs);
|
||||
r3 = vmlaq_f32(r3, values1.val[1], coeffs);
|
||||
}
|
||||
src += 2;
|
||||
in += 4;
|
||||
|
||||
float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))};
|
||||
float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))};
|
||||
|
||||
vst1_f32(&dst[pos], r2);
|
||||
pos += 2;
|
||||
} while(--todo);
|
||||
vtranspose4(r0, r1, r2, r3);
|
||||
return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3));
|
||||
});
|
||||
}
|
||||
if((dst.size()&1))
|
||||
if(const std::size_t todo{dst.size()&3})
|
||||
{
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
++in;
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
std::generate(dst.begin(), dst.end(), [&in,this]
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(std::size_t j{0};j < mCoeffs.size();++j)
|
||||
ret += src[j*2] * mCoeffs[j];
|
||||
|
||||
output = ret;
|
||||
++src;
|
||||
}
|
||||
ret += in[j*2] * mCoeffs[j];
|
||||
++in;
|
||||
return ret;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ namespace {
|
||||
|
||||
constexpr double Epsilon{1e-9};
|
||||
|
||||
#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.
|
||||
@@ -33,7 +29,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"};
|
||||
@@ -57,7 +53,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.
|
||||
*
|
||||
@@ -89,7 +84,7 @@ double Kaiser(const double beta, const double k, const double besseli_0_beta)
|
||||
{
|
||||
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 size (order) of the Kaiser window. Rejection is in dB and
|
||||
@@ -130,10 +125,10 @@ constexpr double CalcKaiserBeta(const double rejection)
|
||||
* p -- gain compensation factor when sampling
|
||||
* f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
|
||||
*/
|
||||
double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
|
||||
const double cutoff, const uint i)
|
||||
auto SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
|
||||
const double cutoff, const uint i) -> double
|
||||
{
|
||||
const double x{static_cast<double>(i) - l};
|
||||
const auto x = static_cast<double>(i) - l;
|
||||
return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
|
||||
}
|
||||
|
||||
@@ -143,77 +138,83 @@ double SincFilter(const uint l, const double beta, const double besseli_0_beta,
|
||||
// that's used to cut frequencies above the destination nyquist.
|
||||
void PPhaseResampler::init(const uint srcRate, const uint dstRate)
|
||||
{
|
||||
const uint gcd{std::gcd(srcRate, dstRate)};
|
||||
const auto gcd = std::gcd(srcRate, dstRate);
|
||||
mP = dstRate / gcd;
|
||||
mQ = srcRate / gcd;
|
||||
|
||||
/* The cutoff is adjusted by half the transition width, so the transition
|
||||
* ends before the nyquist (0.5). Both are scaled by the downsampling
|
||||
* factor.
|
||||
/* The cutoff is adjusted by the transition width, so the transition ends
|
||||
* at nyquist (0.5). Both are scaled by the downsampling factor.
|
||||
*/
|
||||
const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP)
|
||||
: std::make_tuple(0.475 / mQ, 0.05 / mQ);
|
||||
const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.47 / mP, 0.03 / mP)
|
||||
: std::make_tuple(0.47 / mQ, 0.03 / mQ);
|
||||
|
||||
// A rejection of -180 dB is used for the stop band. Round up when
|
||||
// calculating the left offset to avoid increasing the transition width.
|
||||
const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
|
||||
const double beta{CalcKaiserBeta(180.0)};
|
||||
const double besseli_0_beta{cyl_bessel_i(0, beta)};
|
||||
mM = l*2 + 1;
|
||||
static constexpr auto rejection = 180.0;
|
||||
const auto l = (CalcKaiserOrder(rejection, width)+1u) / 2u;
|
||||
const auto beta = CalcKaiserBeta(rejection);
|
||||
const auto besseli_0_beta = ::cyl_bessel_i(0, beta);
|
||||
mM = l*2u + 1u;
|
||||
mL = l;
|
||||
mF.resize(mM);
|
||||
for(uint i{0};i < mM;i++)
|
||||
mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i);
|
||||
mF[i] = SincFilter(mL, beta, besseli_0_beta, mP, cutoff, i);
|
||||
}
|
||||
|
||||
// Perform the upsample-filter-downsample resampling operation using a
|
||||
// polyphase filter implementation.
|
||||
void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out)
|
||||
void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out) const
|
||||
{
|
||||
if(out.empty()) UNLIKELY
|
||||
return;
|
||||
|
||||
// Handle in-place operation.
|
||||
std::vector<double> workspace;
|
||||
al::span work{out};
|
||||
auto workspace = std::vector<double>{};
|
||||
auto work = al::span{out};
|
||||
if(work.data() == in.data()) UNLIKELY
|
||||
{
|
||||
workspace.resize(out.size());
|
||||
work = workspace;
|
||||
}
|
||||
|
||||
// Resample the input.
|
||||
const uint p{mP}, q{mQ}, m{mM}, l{mL};
|
||||
const al::span<const double> f{mF};
|
||||
for(uint i{0};i < out.size();i++)
|
||||
const auto f = al::span<const double>{mF};
|
||||
const auto p = size_t{mP};
|
||||
const auto q = size_t{mQ};
|
||||
const auto m = size_t{mM};
|
||||
/* Input starts at l to compensate for the filter delay. This will drop any
|
||||
* build-up from the first half of the filter.
|
||||
*/
|
||||
auto l = size_t{mL};
|
||||
std::generate(work.begin(), work.end(), [in,f,p,q,m,&l]
|
||||
{
|
||||
// Input starts at l to compensate for the filter delay. This will
|
||||
// drop any build-up from the first half of the filter.
|
||||
std::size_t j_f{(l + q*i) % p};
|
||||
std::size_t j_s{(l + q*i) / p};
|
||||
auto j_s = l / p;
|
||||
auto j_f = l % p;
|
||||
l += q;
|
||||
|
||||
// Only take input when 0 <= j_s < in.size().
|
||||
double r{0.0};
|
||||
if(j_f < m) LIKELY
|
||||
if(j_f >= m) UNLIKELY
|
||||
return 0.0;
|
||||
|
||||
auto filt_len = (m - j_f - 1)/p + 1;
|
||||
if(j_s+1 > in.size()) LIKELY
|
||||
{
|
||||
std::size_t filt_len{(m-j_f+p-1) / p};
|
||||
if(j_s+1 > in.size()) LIKELY
|
||||
{
|
||||
std::size_t skip{std::min(j_s+1 - in.size(), filt_len)};
|
||||
j_f += p*skip;
|
||||
j_s -= skip;
|
||||
filt_len -= skip;
|
||||
}
|
||||
std::size_t todo{std::min(j_s+1, filt_len)};
|
||||
while(todo)
|
||||
{
|
||||
r += f[j_f] * in[j_s];
|
||||
j_f += p; --j_s;
|
||||
--todo;
|
||||
}
|
||||
const auto skip = std::min(j_s+1-in.size(), filt_len);
|
||||
j_f += p*skip;
|
||||
j_s -= skip;
|
||||
filt_len -= skip;
|
||||
}
|
||||
work[i] = r;
|
||||
}
|
||||
/* Get the range of input samples being used for this output sample.
|
||||
* j_s is the first sample and iterates backwards toward 0.
|
||||
*/
|
||||
const auto src = in.first(j_s+1).last(std::min(j_s+1, filt_len));
|
||||
return std::accumulate(src.rbegin(), src.rend(), 0.0, [p,f,&j_f](const double cur,
|
||||
const double smp) -> double
|
||||
{
|
||||
const auto ret = cur + f[j_f]*smp;
|
||||
j_f += p;
|
||||
return ret;
|
||||
});
|
||||
});
|
||||
// Clean up after in-place operation.
|
||||
if(work.data() != out.data())
|
||||
std::copy(work.cbegin(), work.cend(), out.begin());
|
||||
|
||||
@@ -37,7 +37,7 @@ using uint = unsigned int;
|
||||
|
||||
struct PPhaseResampler {
|
||||
void init(const uint srcRate, const uint dstRate);
|
||||
void process(const al::span<const double> in, const al::span<double> out);
|
||||
void process(const al::span<const double> in, const al::span<double> out) const;
|
||||
|
||||
explicit operator bool() const noexcept { return !mF.empty(); }
|
||||
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr
|
||||
@@ -76,8 +77,8 @@ auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz}
|
||||
: std::array{mSizeMask+1 - read_idx, rdend&mSizeMask};
|
||||
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
@@ -99,8 +100,8 @@ auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz}
|
||||
: std::array{mSizeMask+1 - read_idx, rdend&mSizeMask};
|
||||
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
@@ -121,8 +122,8 @@ auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size
|
||||
const std::size_t write_idx{w & mSizeMask};
|
||||
|
||||
const std::size_t wrend{write_idx + to_write};
|
||||
const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask);
|
||||
const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::array{to_write, 0_uz}
|
||||
: std::array{mSizeMask+1 - write_idx, wrend&mSizeMask};
|
||||
|
||||
auto srcbytes = al::span{static_cast<const std::byte*>(src), count*mElemSize};
|
||||
std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize));
|
||||
@@ -146,10 +147,10 @@ auto RingBuffer::getReadVector() noexcept -> DataPair
|
||||
/* Two part vector: the rest of the buffer after the current read ptr,
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx},
|
||||
{mBuffer.data(), rdend&mSizeMask}};
|
||||
return DataPair{{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx},
|
||||
{mBuffer.data(), rdend&mSizeMask}}};
|
||||
}
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}};
|
||||
return DataPair{{{mBuffer.data() + read_idx*mElemSize, readable}, {}}};
|
||||
}
|
||||
|
||||
auto RingBuffer::getWriteVector() noexcept -> DataPair
|
||||
@@ -165,8 +166,8 @@ auto RingBuffer::getWriteVector() noexcept -> DataPair
|
||||
/* Two part vector: the rest of the buffer after the current write ptr,
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx},
|
||||
{mBuffer.data(), wrend&mSizeMask}};
|
||||
return DataPair{{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx},
|
||||
{mBuffer.data(), wrend&mSizeMask}}};
|
||||
}
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}};
|
||||
return DataPair{{{mBuffer.data() + write_idx*mElemSize, writable}, {}}};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
@@ -40,7 +39,7 @@ public:
|
||||
std::byte *buf;
|
||||
std::size_t len;
|
||||
};
|
||||
using DataPair = std::pair<Data,Data>;
|
||||
using DataPair = std::array<Data,2>;
|
||||
|
||||
RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize,
|
||||
const std::size_t numbytes)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "alstring.h"
|
||||
|
||||
/* NOLINTBEGIN(bugprone-suspicious-stringview-data-usage) */
|
||||
std::string wstr_to_utf8(std::wstring_view wstr)
|
||||
{
|
||||
std::string ret;
|
||||
@@ -40,6 +41,7 @@ std::wstring utf8_to_wstr(std::string_view str)
|
||||
|
||||
return ret;
|
||||
}
|
||||
/* NOLINTEND(bugprone-suspicious-stringview-data-usage) */
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <cwchar>
|
||||
#include <string_view>
|
||||
#include <wchar.h>
|
||||
|
||||
std::string wstr_to_utf8(std::wstring_view wstr);
|
||||
std::wstring utf8_to_wstr(std::string_view str);
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
#ifndef COMMON_VECMAT_H
|
||||
#define COMMON_VECMAT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
namespace alu {
|
||||
|
||||
template<typename T>
|
||||
class VectorR {
|
||||
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
|
||||
alignas(16) std::array<T,4> mVals;
|
||||
class Vector {
|
||||
alignas(16) std::array<float,4> mVals{};
|
||||
|
||||
public:
|
||||
constexpr VectorR() noexcept = default;
|
||||
constexpr VectorR(const VectorR&) noexcept = default;
|
||||
constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { }
|
||||
constexpr Vector() noexcept = default;
|
||||
constexpr Vector(const Vector&) noexcept = default;
|
||||
constexpr Vector(Vector&&) noexcept = default;
|
||||
constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { }
|
||||
|
||||
constexpr VectorR& operator=(const VectorR&) noexcept = default;
|
||||
constexpr auto operator=(const Vector&) noexcept -> Vector& = default;
|
||||
constexpr auto operator=(Vector&&) noexcept -> Vector& = default;
|
||||
|
||||
constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; }
|
||||
constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; }
|
||||
|
||||
constexpr VectorR& operator+=(const VectorR &rhs) noexcept
|
||||
constexpr auto operator+=(const Vector &rhs) noexcept -> Vector&
|
||||
{
|
||||
mVals[0] += rhs.mVals[0];
|
||||
mVals[1] += rhs.mVals[1];
|
||||
@@ -36,85 +38,84 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr VectorR operator-(const VectorR &rhs) const noexcept
|
||||
[[nodiscard]] constexpr
|
||||
auto operator-(const Vector &rhs) const noexcept -> Vector
|
||||
{
|
||||
return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]};
|
||||
}
|
||||
|
||||
constexpr T normalize(T limit = std::numeric_limits<T>::epsilon())
|
||||
constexpr auto normalize() -> float
|
||||
{
|
||||
limit = std::max(limit, std::numeric_limits<T>::epsilon());
|
||||
const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
|
||||
if(length_sqr > limit*limit)
|
||||
const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
|
||||
if(length_sqr > std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
const T length{std::sqrt(length_sqr)};
|
||||
T inv_length{T{1}/length};
|
||||
const auto length = std::sqrt(length_sqr);
|
||||
auto inv_length = float{1.0f / length};
|
||||
mVals[0] *= inv_length;
|
||||
mVals[1] *= inv_length;
|
||||
mVals[2] *= inv_length;
|
||||
return length;
|
||||
}
|
||||
mVals[0] = mVals[1] = mVals[2] = T{0};
|
||||
return T{0};
|
||||
mVals[0] = mVals[1] = mVals[2] = 0.0f;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto cross_product(const alu::VectorR<T> &rhs) const noexcept -> VectorR
|
||||
[[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector
|
||||
{
|
||||
return VectorR{
|
||||
return Vector{
|
||||
mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1],
|
||||
mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2],
|
||||
mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0],
|
||||
T{0}};
|
||||
0.0f};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto dot_product(const alu::VectorR<T> &rhs) const noexcept -> T
|
||||
[[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float
|
||||
{ return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; }
|
||||
};
|
||||
using Vector = VectorR<float>;
|
||||
|
||||
template<typename T>
|
||||
class MatrixR {
|
||||
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
|
||||
alignas(16) std::array<T,16> mVals;
|
||||
class Matrix {
|
||||
alignas(16) std::array<float,16> mVals{};
|
||||
|
||||
public:
|
||||
constexpr MatrixR() noexcept = default;
|
||||
constexpr MatrixR(const MatrixR&) noexcept = default;
|
||||
constexpr explicit MatrixR(
|
||||
T aa, T ab, T ac, T ad,
|
||||
T ba, T bb, T bc, T bd,
|
||||
T ca, T cb, T cc, T cd,
|
||||
T da, T db, T dc, T dd) noexcept
|
||||
: mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}
|
||||
constexpr Matrix() noexcept = default;
|
||||
constexpr Matrix(const Matrix&) noexcept = default;
|
||||
constexpr Matrix(Matrix&&) noexcept = default;
|
||||
constexpr explicit Matrix(
|
||||
float aa, float ab, float ac, float ad,
|
||||
float ba, float bb, float bc, float bd,
|
||||
float ca, float cb, float cc, float cd,
|
||||
float da, float db, float dc, float dd) noexcept
|
||||
: mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}}
|
||||
{ }
|
||||
|
||||
constexpr MatrixR& operator=(const MatrixR&) noexcept = default;
|
||||
constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default;
|
||||
constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default;
|
||||
|
||||
constexpr auto operator[](size_t idx) noexcept { return al::span<T,4>{&mVals[idx*4], 4}; }
|
||||
constexpr auto operator[](size_t idx) const noexcept
|
||||
{ return al::span<const T,4>{&mVals[idx*4], 4}; }
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept
|
||||
{ return al::span<float,4>{&mVals[idx*4], 4}; }
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept
|
||||
{ return al::span<const float,4>{&mVals[idx*4], 4}; }
|
||||
|
||||
static constexpr MatrixR Identity() noexcept
|
||||
[[nodiscard]] static constexpr auto Identity() noexcept -> Matrix
|
||||
{
|
||||
return MatrixR{
|
||||
T{1}, T{0}, T{0}, T{0},
|
||||
T{0}, T{1}, T{0}, T{0},
|
||||
T{0}, T{0}, T{1}, T{0},
|
||||
T{0}, T{0}, T{0}, T{1}};
|
||||
return Matrix{
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector
|
||||
{
|
||||
return Vector{
|
||||
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
|
||||
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
|
||||
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
|
||||
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
|
||||
}
|
||||
};
|
||||
using Matrix = MatrixR<float>;
|
||||
|
||||
template<typename T>
|
||||
constexpr VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexcept
|
||||
{
|
||||
return VectorR<T>{
|
||||
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
|
||||
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
|
||||
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
|
||||
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
|
||||
}
|
||||
|
||||
} // namespace alu
|
||||
|
||||
|
||||
Reference in New Issue
Block a user