update OpenAL-Soft to 1.24.3.

This commit is contained in:
Sasha Szpakowski
2025-05-03 12:51:37 -03:00
parent 375c6f88cd
commit 5e4f3241ac
322 changed files with 54386 additions and 12885 deletions
+44
View File
@@ -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 */
+24
View File
@@ -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 */
+11
View File
@@ -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__,
+4 -4
View File
@@ -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)
+61 -15
View File
@@ -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
+24 -14
View File
@@ -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);
}
+2 -3
View File
@@ -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));
}
+1 -1
View File
@@ -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();
+51 -56
View File
@@ -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&>())>>;
+18 -7
View File
@@ -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
+24 -5
View File
@@ -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
+5 -5
View File
@@ -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()
+7 -6
View File
@@ -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
+5 -1
View File
@@ -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 */
+61
View File
@@ -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
+81
View File
@@ -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
+8 -6
View File
@@ -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
+5 -2
View File
@@ -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(); }
+24
View File
@@ -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>
+48 -39
View File
@@ -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) */
+4 -5
View File
@@ -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
+122 -123
View File
@@ -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
}
+52 -51
View File
@@ -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(); }
+14 -13
View File
@@ -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}, {}}};
}
+1 -2
View File
@@ -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)
+2
View File
@@ -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 {
+1 -1
View File
@@ -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);
+61 -60
View File
@@ -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