* BugFix: Correct convexDecomp compilation by setting the LINUX flag when necessary.

* BugFix: Update OpenAL to correct a compilation error on Linux.
This commit is contained in:
Robert MacGregor 2022-05-30 16:32:45 -04:00
parent e071f1d901
commit 7380161054
234 changed files with 30864 additions and 7523 deletions

View file

@ -1,11 +1,11 @@
#ifndef AL_BIT_H
#define AL_BIT_H
#include <cstdint>
#include <limits>
#include <type_traits>
#if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64))
#include <intrin.h>
#include "opthelpers.h"
#endif
namespace al {
@ -21,19 +21,19 @@ enum class endian {
/* This doesn't support mixed-endian. */
namespace detail_ {
constexpr inline bool EndianTest() noexcept
constexpr bool IsLittleEndian() noexcept
{
static_assert(sizeof(char) < sizeof(int), "char is too big");
constexpr int test_val{1};
return static_cast<const char&>(test_val);
return static_cast<const char&>(test_val) ? true : false;
}
} // namespace detail_
enum class endian {
little = 0,
big = 1,
native = detail_::EndianTest() ? little : big
big = 0,
little = 1,
native = detail_::IsLittleEndian() ? little : big
};
#endif
@ -73,6 +73,17 @@ int> countr_zero(T val) noexcept
* they're good enough if the GCC built-ins aren't available.
*/
namespace detail_ {
template<typename T, size_t = std::numeric_limits<T>::digits>
struct fast_utype { };
template<typename T>
struct fast_utype<T,8> { using type = std::uint_fast8_t; };
template<typename T>
struct fast_utype<T,16> { using type = std::uint_fast16_t; };
template<typename T>
struct fast_utype<T,32> { using type = std::uint_fast32_t; };
template<typename T>
struct fast_utype<T,64> { using type = std::uint_fast64_t; };
template<typename T>
constexpr T repbits(unsigned char bits) noexcept
{
@ -85,47 +96,47 @@ namespace detail_ {
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value,
int> popcount(T v) noexcept
int> popcount(T val) noexcept
{
constexpr T m55{detail_::repbits<T>(0x55)};
constexpr T m33{detail_::repbits<T>(0x33)};
constexpr T m0f{detail_::repbits<T>(0x0f)};
constexpr T m01{detail_::repbits<T>(0x01)};
using fast_type = typename detail_::fast_utype<T>::type;
constexpr fast_type b01010101{detail_::repbits<fast_type>(0x55)};
constexpr fast_type b00110011{detail_::repbits<fast_type>(0x33)};
constexpr fast_type b00001111{detail_::repbits<fast_type>(0x0f)};
constexpr fast_type b00000001{detail_::repbits<fast_type>(0x01)};
v = v - ((v >> 1) & m55);
v = (v & m33) + ((v >> 2) & m33);
v = (v + (v >> 4)) & m0f;
return static_cast<int>((v * m01) >> ((sizeof(T)-1)*8));
fast_type v{fast_type{val} - ((fast_type{val} >> 1) & b01010101)};
v = (v & b00110011) + ((v >> 2) & b00110011);
v = (v + (v >> 4)) & b00001111;
return static_cast<int>(((v * b00000001) >> ((sizeof(T)-1)*8)) & 0xff);
}
#if defined(_WIN64)
#ifdef _WIN32
template<typename T>
inline std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value,
inline std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value
&& std::numeric_limits<T>::digits <= 32,
int> countr_zero(T v)
{
unsigned long idx{std::numeric_limits<T>::digits};
if_constexpr(std::numeric_limits<T>::digits <= 32)
_BitScanForward(&idx, static_cast<uint32_t>(v));
else // std::numeric_limits<T>::digits > 32
_BitScanForward64(&idx, v);
_BitScanForward(&idx, static_cast<uint32_t>(v));
return static_cast<int>(idx);
}
#elif defined(_WIN32)
template<typename T>
inline std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value,
inline std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value
&& 32 < std::numeric_limits<T>::digits && std::numeric_limits<T>::digits <= 64,
int> countr_zero(T v)
{
unsigned long idx{std::numeric_limits<T>::digits};
if_constexpr(std::numeric_limits<T>::digits <= 32)
_BitScanForward(&idx, static_cast<uint32_t>(v));
else if(!_BitScanForward(&idx, static_cast<uint32_t>(v)))
#ifdef _WIN64
_BitScanForward64(&idx, v);
#else
if(!_BitScanForward(&idx, static_cast<uint32_t>(v)))
{
if(_BitScanForward(&idx, static_cast<uint32_t>(v>>32)))
idx += 32;
}
#endif /* _WIN64 */
return static_cast<int>(idx);
}

View file

@ -10,57 +10,7 @@ using uint = unsigned int;
namespace al {
/* The "canonical" way to store raw byte data. Like C++17's std::byte, it's not
* treated as a character type and does not work with arithmatic ops. Only
* bitwise ops are allowed.
*/
enum class byte : unsigned char { };
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value,T>
to_integer(al::byte b) noexcept { return T(b); }
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte>
operator<<(al::byte lhs, T rhs) noexcept { return al::byte(to_integer<uint>(lhs) << rhs); }
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte>
operator>>(al::byte lhs, T rhs) noexcept { return al::byte(to_integer<uint>(lhs) >> rhs); }
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte&>
operator<<=(al::byte &lhs, T rhs) noexcept { lhs = lhs << rhs; return lhs; }
template<typename T>
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte&>
operator>>=(al::byte &lhs, T rhs) noexcept { lhs = lhs >> rhs; return lhs; }
#define AL_DECL_OP(op, opeq) \
template<typename T> \
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte> \
operator op (al::byte lhs, T rhs) noexcept \
{ return al::byte(to_integer<uint>(lhs) op static_cast<uint>(rhs)); } \
\
template<typename T> \
constexpr std::enable_if_t<std::is_integral<T>::value,al::byte&> \
operator opeq (al::byte &lhs, T rhs) noexcept { lhs = lhs op rhs; return lhs; } \
\
constexpr al::byte operator op (al::byte lhs, al::byte rhs) noexcept \
{ return al::byte(lhs op to_integer<uint>(rhs)); } \
\
constexpr al::byte& operator opeq (al::byte &lhs, al::byte rhs) noexcept \
{ lhs = lhs op rhs; return lhs; }
AL_DECL_OP(|, |=)
AL_DECL_OP(&, &=)
AL_DECL_OP(^, ^=)
#undef AL_DECL_OP
constexpr al::byte operator~(al::byte b) noexcept
{ return al::byte(~to_integer<uint>(b)); }
using byte = unsigned char;
} // namespace al

View file

@ -4,15 +4,95 @@
#include "alcomplex.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <utility>
#include "albit.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "math_defs.h"
#include "opthelpers.h"
namespace {
using ushort = unsigned short;
using ushort2 = std::pair<ushort,ushort>;
/* Because std::array doesn't have constexpr non-const accessors in C++14. */
template<typename T, size_t N>
struct our_array {
T mData[N];
};
constexpr size_t BitReverseCounter(size_t log2_size) noexcept
{
/* Some magic math that calculates the number of swaps needed for a
* sequence of bit-reversed indices when index < reversed_index.
*/
return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u));
}
template<size_t N>
constexpr auto GetBitReverser() noexcept
{
static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table.");
our_array<ushort2, BitReverseCounter(N)> ret{};
const size_t fftsize{1u << N};
size_t ret_i{0};
/* Bit-reversal permutation applied to a sequence of fftsize items. */
for(size_t idx{1u};idx < fftsize-1;++idx)
{
size_t revidx{0u}, imask{idx};
for(size_t i{0};i < N;++i)
{
revidx = (revidx<<1) | (imask&1);
imask >>= 1;
}
if(idx < revidx)
{
ret.mData[ret_i].first = static_cast<ushort>(idx);
ret.mData[ret_i].second = static_cast<ushort>(revidx);
++ret_i;
}
}
assert(ret_i == al::size(ret.mData));
return ret;
}
/* These bit-reversal swap tables support up to 10-bit indices (1024 elements),
* which is the largest used by OpenAL Soft's filters and effects. Larger FFT
* requests, used by some utilities where performance is less important, will
* use a slower table-less path.
*/
constexpr auto BitReverser2 = GetBitReverser<2>();
constexpr auto BitReverser3 = GetBitReverser<3>();
constexpr auto BitReverser4 = GetBitReverser<4>();
constexpr auto BitReverser5 = GetBitReverser<5>();
constexpr auto BitReverser6 = GetBitReverser<6>();
constexpr auto BitReverser7 = GetBitReverser<7>();
constexpr auto BitReverser8 = GetBitReverser<8>();
constexpr auto BitReverser9 = GetBitReverser<9>();
constexpr auto BitReverser10 = GetBitReverser<10>();
constexpr al::span<const ushort2> gBitReverses[11]{
{}, {},
BitReverser2.mData,
BitReverser3.mData,
BitReverser4.mData,
BitReverser5.mData,
BitReverser6.mData,
BitReverser7.mData,
BitReverser8.mData,
BitReverser9.mData,
BitReverser10.mData
};
} // namespace
void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
{
const size_t fftsize{buffer.size()};
@ -21,27 +101,33 @@ void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
*/
const size_t log2_size{static_cast<size_t>(al::countr_zero(fftsize))};
/* Bit-reversal permutation applied to a sequence of fftsize items. */
for(size_t idx{1u};idx < fftsize-1;++idx)
if(unlikely(log2_size >= al::size(gBitReverses)))
{
size_t revidx{0u}, imask{idx};
for(size_t i{0};i < log2_size;++i)
for(size_t idx{1u};idx < fftsize-1;++idx)
{
revidx = (revidx<<1) | (imask&1);
imask >>= 1;
}
size_t revidx{0u}, imask{idx};
for(size_t i{0};i < log2_size;++i)
{
revidx = (revidx<<1) | (imask&1);
imask >>= 1;
}
if(idx < revidx)
std::swap(buffer[idx], buffer[revidx]);
if(idx < revidx)
std::swap(buffer[idx], buffer[revidx]);
}
}
else for(auto &rev : gBitReverses[log2_size])
std::swap(buffer[rev.first], buffer[rev.second]);
/* Iterative form of Danielson-Lanczos lemma */
const double pi{al::numbers::pi * sign};
size_t step2{1u};
for(size_t i{0};i < log2_size;++i)
{
const double arg{al::MathDefs<double>::Pi() / static_cast<double>(step2)};
const double arg{pi / static_cast<double>(step2)};
const std::complex<double> w{std::cos(arg), std::sin(arg)*sign};
/* TODO: Would std::polar(1.0, arg) be any better? */
const std::complex<double> w{std::cos(arg), std::sin(arg)};
std::complex<double> u{1.0, 0.0};
const size_t step{step2 << 1};
for(size_t j{0};j < step2;j++)

View file

@ -13,9 +13,11 @@
#include "pragmadefs.h"
[[gnu::alloc_align(1), gnu::alloc_size(2)]] void *al_malloc(size_t alignment, size_t size);
[[gnu::alloc_align(1), gnu::alloc_size(2)]] void *al_calloc(size_t alignment, size_t size);
void al_free(void *ptr) noexcept;
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
void *al_malloc(size_t alignment, size_t size);
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
void *al_calloc(size_t alignment, size_t size);
#define DISABLE_ALLOC() \
@ -48,8 +50,8 @@ enum FamCount : size_t { };
#define DEF_FAM_NEWDEL(T, FamMem) \
static constexpr size_t Sizeof(size_t count) noexcept \
{ \
return std::max<size_t>(sizeof(T), \
decltype(FamMem)::Sizeof(count, offsetof(T, FamMem))); \
return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \
sizeof(T)); \
} \
\
void *operator new(size_t /*size*/, FamCount count) \
@ -95,12 +97,15 @@ struct allocator {
void deallocate(T *p, std::size_t) noexcept { al_free(p); }
};
template<typename T, std::size_t N, typename U, std::size_t M>
bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
template<typename T, std::size_t N, typename U, std::size_t M>
bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
template<size_t alignment, typename T>
[[gnu::assume_aligned(alignment)]] inline T* assume_aligned(T *ptr) noexcept { return ptr; }
template<typename T, typename ...Args>
constexpr T* construct_at(T *ptr, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
{ return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...}; }
/* At least VS 2015 complains that 'ptr' is unused when the given type's
* destructor is trivial (a no-op). So disable that warning for this call.
@ -114,14 +119,14 @@ destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<T>::value)
DIAGNOSTIC_POP
template<typename T>
constexpr std::enable_if_t<std::is_array<T>::value>
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<T>::value)
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<std::remove_all_extents_t<T>>::value)
{
for(auto &elem : *ptr)
al::destroy_at(std::addressof(elem));
}
template<typename T>
constexpr void destroy(T first, T end)
constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
{
while(first != end)
{
@ -132,7 +137,7 @@ constexpr void destroy(T first, T end)
template<typename T, typename N>
constexpr std::enable_if_t<std::is_integral<N>::value,T>
destroy_n(T first, N count)
destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
{
if(count != 0)
{
@ -146,8 +151,8 @@ destroy_n(T first, N count)
template<typename T, typename N>
inline std::enable_if_t<std::is_integral<N>::value,T>
uninitialized_default_construct_n(T first, N count)
inline std::enable_if_t<std::is_integral<N>::value,
T> uninitialized_default_construct_n(T first, N count)
{
using ValueT = typename std::iterator_traits<T>::value_type;
T current{first};
@ -172,10 +177,7 @@ uninitialized_default_construct_n(T first, N count)
* trivially destructible.
*/
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
struct FlexArrayStorage;
template<typename T, size_t alignment>
struct FlexArrayStorage<T,alignment,true> {
struct FlexArrayStorage {
const size_t mSize;
union {
char mDummy;
@ -184,8 +186,8 @@ struct FlexArrayStorage<T,alignment,true> {
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
{
return std::max<size_t>(offsetof(FlexArrayStorage, mArray) + sizeof(T)*count,
sizeof(FlexArrayStorage)) + base;
const size_t len{sizeof(T)*count};
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
}
FlexArrayStorage(size_t size) : mSize{size}
@ -206,8 +208,8 @@ struct FlexArrayStorage<T,alignment,false> {
static constexpr size_t Sizeof(size_t count, size_t base) noexcept
{
return std::max<size_t>(offsetof(FlexArrayStorage, mArray) + sizeof(T)*count,
sizeof(FlexArrayStorage)) + base;
const size_t len{sizeof(T)*count};
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
}
FlexArrayStorage(size_t size) : mSize{size}
@ -248,7 +250,7 @@ struct FlexArray {
static std::unique_ptr<FlexArray> Create(index_type count)
{
void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))};
return std::unique_ptr<FlexArray>{new(ptr) FlexArray{count}};
return std::unique_ptr<FlexArray>{al::construct_at(static_cast<FlexArray*>(ptr), count)};
}
FlexArray(index_type size) : mStore{size} { }

View file

@ -0,0 +1,36 @@
#ifndef COMMON_ALNUMBERS_H
#define COMMON_ALNUMBERS_H
#include <utility>
namespace al {
namespace numbers {
namespace detail_ {
template<typename T>
using as_fp = std::enable_if_t<std::is_floating_point<T>::value, T>;
} // detail_
template<typename T>
static constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
template<typename T>
static constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
template<typename T>
static constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
template<typename T>
static constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
static constexpr auto pi = pi_v<double>;
static constexpr auto inv_pi = inv_pi_v<double>;
static constexpr auto sqrt2 = sqrt2_v<double>;
static constexpr auto sqrt3 = sqrt3_v<double>;
} // namespace numbers
} // namespace al
#endif /* COMMON_ALNUMBERS_H */

View file

@ -1,6 +1,8 @@
#ifndef AL_NUMERIC_H
#define AL_NUMERIC_H
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#ifdef HAVE_INTRIN_H
@ -67,7 +69,7 @@ constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept
{ return minz(max, maxz(min, val)); }
constexpr inline float lerp(float val1, float val2, float mu) noexcept
constexpr inline float lerpf(float val1, float val2, float mu) noexcept
{ return val1 + (val2-val1)*mu; }
constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept
{
@ -271,4 +273,27 @@ inline float fast_roundf(float f) noexcept
#endif
}
template<typename T>
constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept
{
return std::min(std::max(value, min_value), max_value);
}
// Converts level (mB) to gain.
inline float level_mb_to_gain(float x)
{
if(x <= -10'000.0f)
return 0.0f;
return std::pow(10.0f, x / 2'000.0f);
}
// Converts gain to level (mB).
inline float gain_to_level_mb(float x)
{
if (x <= 0.0f)
return -10'000.0f;
return maxf(std::log10(x) * 2'000.0f, -10'000.0f);
}
#endif /* AL_NUMERIC_H */

View file

@ -15,147 +15,339 @@ struct in_place_t { };
constexpr nullopt_t nullopt{};
constexpr in_place_t in_place{};
#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__))
namespace detail_ {
/* Base storage struct for an optional. Defines a trivial destructor, for types
* that can be trivially destructed.
*/
template<typename T, bool = std::is_trivially_destructible<T>::value>
struct optstore_base {
bool mHasValue{false};
union {
char mDummy{};
T mValue;
};
constexpr optstore_base() noexcept { }
template<typename ...Args>
constexpr explicit optstore_base(in_place_t, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optstore_base() = default;
};
/* Specialization needing a non-trivial destructor. */
template<typename T>
struct optstore_base<T, false> {
bool mHasValue{false};
union {
char mDummy{};
T mValue;
};
constexpr optstore_base() noexcept { }
template<typename ...Args>
constexpr explicit optstore_base(in_place_t, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
};
/* Next level of storage, which defines helpers to construct and destruct the
* stored object.
*/
template<typename T>
struct optstore_helper : public optstore_base<T> {
using optstore_base<T>::optstore_base;
template<typename... Args>
constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
{
al::construct_at(std::addressof(this->mValue), std::forward<Args>(args)...);
this->mHasValue = true;
}
constexpr void reset() noexcept
{
if(this->mHasValue)
al::destroy_at(std::addressof(this->mValue));
this->mHasValue = false;
}
constexpr void assign(const optstore_helper &rhs)
noexcept(std::is_nothrow_copy_constructible<T>::value
&& std::is_nothrow_copy_assignable<T>::value)
{
if(!rhs.mHasValue)
this->reset();
else if(this->mHasValue)
this->mValue = rhs.mValue;
else
this->construct(rhs.mValue);
}
constexpr void assign(optstore_helper&& rhs)
noexcept(std::is_nothrow_move_constructible<T>::value
&& std::is_nothrow_move_assignable<T>::value)
{
if(!rhs.mHasValue)
this->reset();
else if(this->mHasValue)
this->mValue = std::move(rhs.mValue);
else
this->construct(std::move(rhs.mValue));
}
};
/* Define copy and move constructors and assignment operators, which may or may
* not be trivial.
*/
template<typename T, bool trivial_copy = std::is_trivially_copy_constructible<T>::value,
bool trivial_move = std::is_trivially_move_constructible<T>::value,
/* Trivial assignment is dependent on trivial construction+destruction. */
bool = trivial_copy && std::is_trivially_copy_assignable<T>::value
&& std::is_trivially_destructible<T>::value,
bool = trivial_move && std::is_trivially_move_assignable<T>::value
&& std::is_trivially_destructible<T>::value>
struct optional_storage;
template<typename T>
struct optional_storage<T, true> {
bool mHasValue{false};
union {
char mDummy;
T mValue;
};
/* Some versions of GCC have issues with 'this' in the following noexcept(...)
* statements, so this macro is a workaround.
*/
#define _this std::declval<optional_storage*>()
optional_storage() { }
template<typename ...Args>
explicit optional_storage(in_place_t, Args&& ...args)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optional_storage() = default;
/* Completely trivial. */
template<typename T>
struct optional_storage<T, true, true, true, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial move assignment. */
template<typename T>
struct optional_storage<T, false> {
bool mHasValue{false};
union {
char mDummy;
T mValue;
};
optional_storage() { }
template<typename ...Args>
explicit optional_storage(in_place_t, Args&& ...args)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optional_storage() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
struct optional_storage<T, true, true, true, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial move construction. */
template<typename T>
struct optional_storage<T, true, false, true, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial copy assignment. */
template<typename T>
struct optional_storage<T, true, true, false, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial copy construction. */
template<typename T>
struct optional_storage<T, false, true, false, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial assignment. */
template<typename T>
struct optional_storage<T, true, true, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial assignment, non-trivial move construction. */
template<typename T>
struct optional_storage<T, true, false, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial assignment, non-trivial copy construction. */
template<typename T>
struct optional_storage<T, false, true, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Completely non-trivial. */
template<typename T>
struct optional_storage<T, false, false, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
#undef _this
} // namespace detail_
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
template<typename T>
class optional {
using storage_t = optional_storage<T>;
using storage_t = detail_::optional_storage<T>;
storage_t mStore;
template<typename... Args>
void doConstruct(Args&& ...args)
{
::new(std::addressof(mStore.mValue)) T{std::forward<Args>(args)...};
mStore.mHasValue = true;
}
storage_t mStore{};
public:
using value_type = T;
optional() = default;
optional(nullopt_t) noexcept { }
optional(const optional &rhs) { if(rhs) doConstruct(*rhs); }
optional(optional&& rhs) { if(rhs) doConstruct(std::move(*rhs)); }
constexpr optional() = default;
constexpr optional(const optional&) = default;
constexpr optional(optional&&) = default;
constexpr optional(nullopt_t) noexcept { }
template<typename ...Args>
explicit optional(in_place_t, Args&& ...args)
constexpr explicit optional(in_place_t, Args&& ...args)
NOEXCEPT_AS(storage_t{al::in_place, std::forward<Args>(args)...})
: mStore{al::in_place, std::forward<Args>(args)...}
{ }
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& std::is_convertible<U&&, T>::value)>
constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
: mStore{al::in_place, std::forward<U>(rhs)}
{ }
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& !std::is_convertible<U&&, T>::value)>
constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
: mStore{al::in_place, std::forward<U>(rhs)}
{ }
~optional() = default;
optional& operator=(nullopt_t) noexcept { reset(); return *this; }
std::enable_if_t<std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value,
optional&> operator=(const optional &rhs)
{
if(!rhs)
reset();
else if(*this)
mStore.mValue = *rhs;
else
doConstruct(*rhs);
return *this;
}
std::enable_if_t<std::is_move_constructible<T>::value && std::is_move_assignable<T>::value,
optional&> operator=(optional&& rhs)
{
if(!rhs)
reset();
else if(*this)
mStore.mValue = std::move(*rhs);
else
doConstruct(std::move(*rhs));
return *this;
}
constexpr optional& operator=(const optional&) = default;
constexpr optional& operator=(optional&&) = default;
constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; }
template<typename U=T>
std::enable_if_t<std::is_constructible<T, U>::value
constexpr std::enable_if_t<std::is_constructible<T, U>::value
&& std::is_assignable<T&, U>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& (!std::is_same<std::decay_t<U>, T>::value || !std::is_scalar<U>::value),
optional&> operator=(U&& rhs)
{
if(*this)
if(mStore.mHasValue)
mStore.mValue = std::forward<U>(rhs);
else
doConstruct(std::forward<U>(rhs));
mStore.construct(std::forward<U>(rhs));
return *this;
}
const T* operator->() const { return std::addressof(mStore.mValue); }
T* operator->() { return std::addressof(mStore.mValue); }
const T& operator*() const& { return this->mValue; }
T& operator*() & { return mStore.mValue; }
const T&& operator*() const&& { return std::move(mStore.mValue); }
T&& operator*() && { return std::move(mStore.mValue); }
constexpr const T* operator->() const { return std::addressof(mStore.mValue); }
constexpr T* operator->() { return std::addressof(mStore.mValue); }
constexpr const T& operator*() const& { return mStore.mValue; }
constexpr T& operator*() & { return mStore.mValue; }
constexpr const T&& operator*() const&& { return std::move(mStore.mValue); }
constexpr T&& operator*() && { return std::move(mStore.mValue); }
operator bool() const noexcept { return mStore.mHasValue; }
bool has_value() const noexcept { return mStore.mHasValue; }
constexpr explicit operator bool() const noexcept { return mStore.mHasValue; }
constexpr bool has_value() const noexcept { return mStore.mHasValue; }
T& value() & { return mStore.mValue; }
const T& value() const& { return mStore.mValue; }
T&& value() && { return std::move(mStore.mValue); }
const T&& value() const&& { return std::move(mStore.mValue); }
constexpr T& value() & { return mStore.mValue; }
constexpr const T& value() const& { return mStore.mValue; }
constexpr T&& value() && { return std::move(mStore.mValue); }
constexpr const T&& value() const&& { return std::move(mStore.mValue); }
template<typename U>
T value_or(U&& defval) const&
constexpr T value_or(U&& defval) const&
{ return bool{*this} ? **this : static_cast<T>(std::forward<U>(defval)); }
template<typename U>
T value_or(U&& defval) &&
constexpr T value_or(U&& defval) &&
{ return bool{*this} ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
void reset() noexcept
template<typename ...Args>
constexpr T& emplace(Args&& ...args)
{
if(mStore.mHasValue)
al::destroy_at(std::addressof(mStore.mValue));
mStore.mHasValue = false;
mStore.reset();
mStore.construct(std::forward<Args>(args)...);
return mStore.mValue;
}
template<typename U, typename ...Args>
constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
T&> emplace(std::initializer_list<U> il, Args&& ...args)
{
mStore.reset();
mStore.construct(il, std::forward<Args>(args)...);
return mStore.mValue;
}
constexpr void reset() noexcept { mStore.reset(); }
};
template<typename T>
inline optional<std::decay_t<T>> make_optional(T&& arg)
constexpr optional<std::decay_t<T>> make_optional(T&& arg)
{ return optional<std::decay_t<T>>{in_place, std::forward<T>(arg)}; }
template<typename T, typename... Args>
inline optional<T> make_optional(Args&& ...args)
constexpr optional<T> make_optional(Args&& ...args)
{ return optional<T>{in_place, std::forward<Args>(args)...}; }
template<typename T, typename U, typename... Args>
inline optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
constexpr optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
{ return optional<T>{in_place, il, std::forward<Args>(args)...}; }
#undef REQUIRES
#undef NOEXCEPT_AS
} // namespace al
#endif /* AL_OPTIONAL_H */

View file

@ -9,22 +9,14 @@
namespace al {
template<typename T>
constexpr auto size(T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size())
{ return cont.size(); }
template<typename T>
constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size())
{ return cont.size(); }
template<typename T, size_t N>
constexpr size_t size(T (&)[N]) noexcept
constexpr size_t size(const T (&)[N]) noexcept
{ return N; }
template<typename T>
constexpr size_t size(std::initializer_list<T> list) noexcept
{ return list.size(); }
template<typename T>
constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())

View file

@ -2,23 +2,10 @@
#define AL_STRING_H
#include <cstddef>
#include <cstring>
#include <string>
#include "almalloc.h"
namespace al {
template<typename T, typename Tr=std::char_traits<T>>
using basic_string = std::basic_string<T, Tr, al::allocator<T>>;
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
using u16string = basic_string<char16_t>;
using u32string = basic_string<char32_t>;
/* These would be better served by using a string_view-like span/view with
* case-insensitive char traits.
*/

View file

@ -0,0 +1,68 @@
#ifndef COMMON_COMPTR_H
#define COMMON_COMPTR_H
#include <cstddef>
#include <utility>
#include "opthelpers.h"
template<typename T>
class ComPtr {
T *mPtr{nullptr};
public:
ComPtr() noexcept = default;
ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
ComPtr(std::nullptr_t) noexcept { }
explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
~ComPtr() { if(mPtr) mPtr->Release(); }
ComPtr& operator=(const ComPtr &rhs)
{
if(!rhs.mPtr)
{
if(mPtr)
mPtr->Release();
mPtr = nullptr;
}
else
{
rhs.mPtr->AddRef();
try {
if(mPtr)
mPtr->Release();
mPtr = rhs.mPtr;
}
catch(...) {
rhs.mPtr->Release();
throw;
}
}
return *this;
}
ComPtr& operator=(ComPtr&& rhs)
{
if(likely(&rhs != this))
{
if(mPtr) mPtr->Release();
mPtr = std::exchange(rhs.mPtr, nullptr);
}
return *this;
}
explicit operator bool() const noexcept { return mPtr != nullptr; }
T& operator*() const noexcept { return *mPtr; }
T* operator->() const noexcept { return mPtr; }
T* get() const noexcept { return mPtr; }
T** getPtr() noexcept { return &mPtr; }
T* release() noexcept { return std::exchange(mPtr, nullptr); }
void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
};
#endif

View file

@ -1,6 +1,8 @@
#ifndef INTRUSIVE_PTR_H
#define INTRUSIVE_PTR_H
#include <utility>
#include "atomic.h"
#include "opthelpers.h"
@ -60,6 +62,8 @@ public:
intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept
{
static_assert(noexcept(std::declval<T*>()->release()), "release must be noexcept");
if(rhs.mPtr) rhs.mPtr->add_ref();
if(mPtr) mPtr->release();
mPtr = rhs.mPtr;
@ -67,14 +71,15 @@ public:
}
intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept
{
if(mPtr)
mPtr->release();
mPtr = rhs.mPtr;
rhs.mPtr = nullptr;
if(likely(&rhs != this))
{
if(mPtr) mPtr->release();
mPtr = std::exchange(rhs.mPtr, nullptr);
}
return *this;
}
operator bool() const noexcept { return mPtr != nullptr; }
explicit operator bool() const noexcept { return mPtr != nullptr; }
T& operator*() const noexcept { return *mPtr; }
T* operator->() const noexcept { return mPtr; }
@ -87,12 +92,7 @@ public:
mPtr = ptr;
}
T* release() noexcept
{
T *ret{mPtr};
mPtr = nullptr;
return ret;
}
T* release() noexcept { return std::exchange(mPtr, nullptr); }
void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }

View file

@ -1,26 +0,0 @@
#ifndef AL_MATH_DEFS_H
#define AL_MATH_DEFS_H
constexpr float Deg2Rad(float x) noexcept { return x * 1.74532925199432955e-02f/*pi/180*/; }
constexpr float Rad2Deg(float x) noexcept { return x * 5.72957795130823229e+01f/*180/pi*/; }
namespace al {
template<typename Real>
struct MathDefs { };
template<>
struct MathDefs<float> {
static constexpr float Pi() noexcept { return 3.14159265358979323846e+00f; }
static constexpr float Tau() noexcept { return 6.28318530717958647692e+00f; }
};
template<>
struct MathDefs<double> {
static constexpr double Pi() noexcept { return 3.14159265358979323846e+00; }
static constexpr double Tau() noexcept { return 6.28318530717958647692e+00; }
};
} // namespace al
#endif /* AL_MATH_DEFS_H */

View file

@ -1,26 +1,50 @@
#ifndef OPTHELPERS_H
#define OPTHELPERS_H
#include <cstdint>
#include <utility>
#ifdef __has_builtin
#define HAS_BUILTIN __has_builtin
#else
#define HAS_BUILTIN(x) (0)
#endif
#ifdef __GNUC__
#define force_inline [[gnu::always_inline]]
#elif defined(_MSC_VER)
#define force_inline __forceinline
#else
#define force_inline inline
#endif
#if defined(__GNUC__) || HAS_BUILTIN(__builtin_expect)
/* LIKELY optimizes the case where the condition is true. The condition is not
* required to be true, but it can result in more optimal code for the true
* path at the expense of a less optimal false path.
/* likely() optimizes for the case where the condition is true. The condition
* is not required to be true, but it can result in more optimal code for the
* true path at the expense of a less optimal false path.
*/
#define LIKELY(x) (__builtin_expect(!!(x), !false))
/* The opposite of LIKELY, optimizing the case where the condition is false. */
#define UNLIKELY(x) (__builtin_expect(!!(x), false))
template<typename T>
force_inline constexpr bool likely(T&& expr) noexcept
{ return __builtin_expect(static_cast<bool>(std::forward<T>(expr)), true); }
/* The opposite of likely(), optimizing for the case where the condition is
* false.
*/
template<typename T>
force_inline constexpr bool unlikely(T&& expr) noexcept
{ return __builtin_expect(static_cast<bool>(std::forward<T>(expr)), false); }
#else
#define LIKELY(x) (!!(x))
#define UNLIKELY(x) (!!(x))
template<typename T>
force_inline constexpr bool likely(T&& expr) noexcept
{ return static_cast<bool>(std::forward<T>(expr)); }
template<typename T>
force_inline constexpr bool unlikely(T&& expr) noexcept
{ return static_cast<bool>(std::forward<T>(expr)); }
#endif
#define LIKELY(x) (likely(x))
#define UNLIKELY(x) (unlikely(x))
#if HAS_BUILTIN(__builtin_assume)
/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes
@ -31,15 +55,33 @@
#elif defined(_MSC_VER)
#define ASSUME __assume
#elif defined(__GNUC__)
#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0)
#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0)
#else
#define ASSUME(x) ((void)0)
#endif
#if __cplusplus >= 201703L || defined(__cpp_if_constexpr)
#define if_constexpr if constexpr
namespace al {
template<std::size_t alignment, typename T>
force_inline constexpr auto assume_aligned(T *ptr) noexcept
{
#ifdef __cpp_lib_assume_aligned
return std::assume_aligned<alignment,T>(ptr);
#elif defined(__clang__) || (defined(__GNUC__) && !defined(__ICC))
return static_cast<T*>(__builtin_assume_aligned(ptr, alignment));
#elif defined(_MSC_VER)
constexpr std::size_t alignment_mask{(1<<alignment) - 1};
if((reinterpret_cast<std::uintptr_t>(ptr)&alignment_mask) == 0)
return ptr;
__assume(0);
#elif defined(__ICC)
__assume_aligned(ptr, alignment);
return ptr;
#else
#define if_constexpr if
return ptr;
#endif
}
} // namespace al
#endif /* OPTHELPERS_H */

View file

@ -0,0 +1,314 @@
#ifndef PHASE_SHIFTER_H
#define PHASE_SHIFTER_H
#ifdef HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
#elif defined(HAVE_NEON)
#include <arm_neon.h>
#endif
#include <array>
#include <stddef.h>
#include "alcomplex.h"
#include "alspan.h"
/* 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<size_t FilterSize>
struct 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 size_t fft_size{FilterSize};
constexpr size_t half_size{fft_size / 2};
auto fftBuffer = std::make_unique<complex_d[]>(fft_size);
std::fill_n(fftBuffer.get(), fft_size, complex_d{});
fftBuffer[half_size] = 1.0;
forward_fft({fftBuffer.get(), fft_size});
for(size_t i{0};i < half_size+1;++i)
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
for(size_t i{half_size+1};i < fft_size;++i)
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
inverse_fft({fftBuffer.get(), fft_size});
auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1);
for(float &coeff : mCoeffs)
{
coeff = static_cast<float>(fftiter->real() / double{fft_size});
fftiter -= 2;
}
}
void process(al::span<float> dst, const float *RESTRICT src) const;
void processAccum(al::span<float> dst, const float *RESTRICT src) const;
private:
#if defined(HAVE_NEON)
/* There doesn't seem to be NEON intrinsics to do this kind of stipple
* shuffling, so there's two custom methods for it.
*/
static auto shuffle_2020(float32x4_t a, float32x4_t b)
{
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))};
ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3);
return ret;
}
static auto shuffle_3131(float32x4_t a, float32x4_t b)
{
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))};
ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3);
return ret;
}
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]);
}
static auto load4(float32_t a, float32_t b, float32_t c, float32_t d)
{
float32x4_t ret{vmovq_n_f32(a)};
ret = vsetq_lane_f32(b, ret, 1);
ret = vsetq_lane_f32(c, ret, 2);
ret = vsetq_lane_f32(d, ret, 3);
return ret;
}
#endif
};
template<size_t S>
inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT src) const
{
#ifdef HAVE_SSE_INTRINSICS
if(size_t todo{dst.size()>>1})
{
auto *out = reinterpret_cast<__m64*>(dst.data());
do {
__m128 r04{_mm_setzero_ps()};
__m128 r14{_mm_setzero_ps()};
for(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])};
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
r04 = _mm_add_ps(r04, _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));
}
src += 2;
__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);
}
if((dst.size()&1))
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < mCoeffs.size();j+=4)
{
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);
}
#elif defined(HAVE_NEON)
size_t pos{0};
if(size_t todo{dst.size()>>1})
{
do {
float32x4_t r04{vdupq_n_f32(0.0f)};
float32x4_t r14{vdupq_n_f32(0.0f)};
for(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])};
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
}
src += 2;
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);
}
if((dst.size()&1))
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < mCoeffs.size();j+=4)
{
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);
}
#else
for(float &output : dst)
{
float ret{0.0f};
for(size_t j{0};j < mCoeffs.size();++j)
ret += src[j*2] * mCoeffs[j];
output = ret;
++src;
}
#endif
}
template<size_t S>
inline void PhaseShifterT<S>::processAccum(al::span<float> dst, const float *RESTRICT src) const
{
#ifdef HAVE_SSE_INTRINSICS
if(size_t todo{dst.size()>>1})
{
auto *out = reinterpret_cast<__m64*>(dst.data());
do {
__m128 r04{_mm_setzero_ps()};
__m128 r14{_mm_setzero_ps()};
for(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])};
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
r04 = _mm_add_ps(r04, _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));
}
src += 2;
__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, _mm_add_ps(_mm_loadl_pi(_mm_undefined_ps(), out), r4));
++out;
} while(--todo);
}
if((dst.size()&1))
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < mCoeffs.size();j+=4)
{
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);
}
#elif defined(HAVE_NEON)
size_t pos{0};
if(size_t todo{dst.size()>>1})
{
do {
float32x4_t r04{vdupq_n_f32(0.0f)};
float32x4_t r14{vdupq_n_f32(0.0f)};
for(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])};
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
}
src += 2;
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], vadd_f32(vld1_f32(&dst[pos]), r2));
pos += 2;
} while(--todo);
}
if((dst.size()&1))
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < mCoeffs.size();j+=4)
{
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);
}
#else
for(float &output : dst)
{
float ret{0.0f};
for(size_t j{0};j < mCoeffs.size();++j)
ret += src[j*2] * mCoeffs[j];
output += ret;
++src;
}
#endif
}
#endif /* PHASE_SHIFTER_H */

View file

@ -4,7 +4,7 @@
#include <algorithm>
#include <cmath>
#include "math_defs.h"
#include "alnumbers.h"
#include "opthelpers.h"
@ -21,9 +21,9 @@ using uint = unsigned int;
*/
double Sinc(const double x)
{
if UNLIKELY(std::abs(x) < Epsilon)
if(unlikely(std::abs(x) < Epsilon))
return 1.0;
return std::sin(al::MathDefs<double>::Pi()*x) / (al::MathDefs<double>::Pi()*x);
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
}
/* The zero-order modified Bessel function of the first kind, used for the
@ -95,7 +95,7 @@ constexpr uint Gcd(uint x, uint y)
*/
constexpr uint CalcKaiserOrder(const double rejection, const double transition)
{
const double w_t{2.0 * al::MathDefs<double>::Pi() * transition};
const double w_t{2.0 * al::numbers::pi * transition};
if LIKELY(rejection > 21.0)
return static_cast<uint>(std::ceil((rejection - 7.95) / (2.285 * w_t)));
return static_cast<uint>(std::ceil(5.79 / w_t));

View file

@ -160,9 +160,9 @@ size_t RingBuffer::write(const void *src, size_t cnt) noexcept
}
ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept
auto RingBuffer::getReadVector() const noexcept -> DataPair
{
ll_ringbuffer_data_pair ret;
DataPair ret;
size_t w{mWritePtr.load(std::memory_order_acquire)};
size_t r{mReadPtr.load(std::memory_order_acquire)};
@ -192,9 +192,9 @@ ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept
return ret;
}
ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept
auto RingBuffer::getWriteVector() const noexcept -> DataPair
{
ll_ringbuffer_data_pair ret;
DataPair ret;
size_t w{mWritePtr.load(std::memory_order_acquire)};
size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};

View file

@ -16,13 +16,6 @@
* single-consumer/single-provider operation.
*/
struct ll_ringbuffer_data {
al::byte *buf;
size_t len;
};
using ll_ringbuffer_data_pair = std::pair<ll_ringbuffer_data,ll_ringbuffer_data>;
struct RingBuffer {
private:
std::atomic<size_t> mWritePtr{0u};
@ -34,6 +27,13 @@ private:
al::FlexArray<al::byte, 16> mBuffer;
public:
struct Data {
al::byte *buf;
size_t len;
};
using DataPair = std::pair<Data,Data>;
RingBuffer(const size_t count) : mBuffer{count} { }
/** Reset the read and write pointers to zero. This is not thread safe. */
@ -44,13 +44,13 @@ public:
* hold the current readable data. If the readable data is in one segment
* the second segment has zero length.
*/
ll_ringbuffer_data_pair getReadVector() const noexcept;
DataPair getReadVector() const noexcept;
/**
* The non-copying data writer. Returns two ringbuffer data pointers that
* hold the current writeable data. If the writeable data is in one segment
* the second segment has zero length.
*/
ll_ringbuffer_data_pair getWriteVector() const noexcept;
DataPair getWriteVector() const noexcept;
/**
* Return the number of elements available for reading. This is the number
@ -98,6 +98,8 @@ public:
void writeAdvance(size_t cnt) noexcept
{ mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); }
size_t getElemSize() const noexcept { return mElemSize; }
/**
* Create a new ringbuffer to hold at least `sz' elements of `elem_sz'
* bytes. The number of elements is rounded up to the next power of two

View file

@ -90,30 +90,43 @@ bool semaphore::try_wait() noexcept
#else
#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP)
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
#include <tuple>
namespace {
using setname_t1 = int(*)(const char*);
using setname_t2 = int(*)(pthread_t, const char*);
using setname_t3 = int(*)(pthread_t, const char*, void*);
void setname_caller(setname_t1 func, const char *name)
{ func(name); }
void setname_caller(setname_t2 func, const char *name)
{ func(pthread_self(), name); }
void setname_caller(setname_t3 func, const char *name)
{ func(pthread_self(), "%s", static_cast<void*>(const_cast<char*>(name))); }
} // namespace
void althrd_setname(const char *name)
{
#if defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(pthread_self(), name);
#elif defined(PTHREAD_SETNAME_NP_ONE_PARAM)
pthread_setname_np(name);
#elif defined(PTHREAD_SETNAME_NP_THREE_PARAMS)
pthread_setname_np(pthread_self(), "%s", (void*)name);
#else
pthread_setname_np(pthread_self(), name);
setname_caller(pthread_set_name_np, name);
#elif defined(HAVE_PTHREAD_SETNAME_NP)
setname_caller(pthread_setname_np, name);
#endif
/* Avoid unused function/parameter warnings. */
std::ignore = name;
std::ignore = static_cast<void(*)(setname_t1,const char*)>(&setname_caller);
std::ignore = static_cast<void(*)(setname_t2,const char*)>(&setname_caller);
std::ignore = static_cast<void(*)(setname_t3,const char*)>(&setname_caller);
}
#else
void althrd_setname(const char*) { }
#endif
#ifdef __APPLE__
namespace al {

View file

@ -35,11 +35,20 @@ public:
return *this;
}
T normalize()
VectorR operator-(const VectorR &rhs) const noexcept
{
const T length{std::sqrt(mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2])};
if(length > std::numeric_limits<T>::epsilon())
const VectorR ret{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]};
return ret;
}
T normalize(T limit = std::numeric_limits<T>::epsilon())
{
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 T length{std::sqrt(length_sqr)};
T inv_length{T{1}/length};
mVals[0] *= inv_length;
mVals[1] *= inv_length;