Bladeren bron

audio: add saturated addition for samples

wip/image-kernel
Sam Hocevar 9 maanden geleden
bovenliggende
commit
159f422c23
5 gewijzigde bestanden met toevoegingen van 241 en 27 verwijderingen
  1. +56
    -25
      include/lol/private/audio/stream.h
  2. +1
    -1
      t/Makefile
  3. +1
    -1
      t/audio-convert.cpp
  4. +182
    -0
      t/audio-sadd.cpp
  5. +1
    -0
      t/test.cpp

+ 56
- 25
include/lol/private/audio/stream.h Bestand weergeven

@@ -40,7 +40,7 @@ public:

if constexpr (std::is_same_v<FROM, TO> || (from_fp && to_fp))
{
// If types are the same, or both floating-point, no conversion is needed
// If types are the same, or both floating point, no conversion is needed
return TO(x);
}
else if constexpr (from_fp)
@@ -68,8 +68,8 @@ public:
// When converting between integer types, we first convert to an unsigned
// type of same size as source (e.g. int16_t → uint16_t) to ensure that all
// operations will happen modulo n (not guaranteed with signed types).
// The next step is to shift right (drop bits) or promote left (multiplying
// by a magic constant such as 0x1010101 or 0x10001). This happens using the
// The next step is to shift right (drop bits) or promote left (multiply by
// a magic constant such as 0x1010101 or 0x10001). This happens using the
// UBIG type, which is an unsigned integer type at least as large as FROM
// and TO.
// Finally, we convert back to signed (e.g. uint16_t → int16_t) if necessary.
@@ -84,6 +84,49 @@ public:
return TO(tmp + UTO(std::numeric_limits<TO>::min()));
}
}

// Saturated addition for samples
template<typename T>
static inline T sadd(T x, T y)
{
if constexpr (std::is_floating_point_v<T>)
{
// No saturation for floating point types
return x + y;
}
else if constexpr (sizeof(T) <= 4)
{
// For integer types up to 32-bit, do the computation with a larger type
using BIG = std::conditional_t<sizeof(T) == 1, int16_t,
std::conditional_t<sizeof(T) == 2, int32_t,
std::conditional_t<sizeof(T) == 4, int64_t, void>>>;
BIG constexpr min = std::numeric_limits<T>::min();
BIG constexpr max = std::numeric_limits<T>::max();
BIG constexpr zero = (min + max + 1) >> 1;

return T(std::max(min, std::min(max, BIG(BIG(x) + BIG(y) - zero))));
}
else if constexpr (std::is_unsigned_v<T>)
{
// Unsigned saturated add for 64-bit and larger: clamp according to overflow
T constexpr zero = T(1) << 8 * sizeof(T) - 1;
T constexpr minus_one = zero - T(1);
T ret = x + y;
return ret >= x ? std::max(zero, ret) - zero : std::min(minus_one, ret) + zero;
}
else
{
// Signed saturated add for 64-bit and larger: if signs differ, no overflow
// occurred, just return the sum of the arguments; otherwise, clamp according
// to the arguments sign.
using U = std::make_unsigned_t<T>;
U constexpr umax = U(std::numeric_limits<T>::max());
U constexpr umin = U(std::numeric_limits<T>::min());
U ret = U(x) + U(y);

return T(x ^ y) < 0 ? T(ret) : x >= 0 ? T(std::min(ret, umax)) : T(std::max(ret, umin));
}
}
};

template<typename T>
@@ -162,18 +205,12 @@ public:

for (size_t n = 0; n < samples; ++n)
{
T sample = T(0);
T x = T(0);

for (auto const &b : buffers)
sample += b[n];

if constexpr (std::is_same_v<T, float>)
buf[n] = std::min(1.0f, std::max(-1.0f, sample));
else if constexpr (std::is_same_v<T, int16_t>)
buf[n] = std::min(int16_t(32767), std::max(int16_t(-32768), sample));
else if constexpr (std::is_same_v<T, uint16_t>)
buf[n] = std::min(uint16_t(65535), std::max(uint16_t(0), sample));
else
buf[n] = sample;
x = sample::sadd(x, b[n]);

buf[n] = x;
}

return frames;
@@ -236,18 +273,12 @@ public:
{
for (size_t out_ch = 0; out_ch < this->channels(); ++out_ch)
{
T sample(0);
T x(0);

for (size_t in_ch = 0; in_ch < m_in->channels(); ++in_ch)
sample += tmp[f * m_in->channels() + in_ch] * matrix[out_ch * m_in->channels() + in_ch];

if constexpr (std::is_same_v<T, float>)
buf[f * this->channels() + out_ch] = std::min(1.0f, std::max(-1.0f, sample));
else if constexpr (std::is_same_v<T, int16_t>)
buf[f * this->channels() + out_ch] = std::min(int16_t(32767), std::max(int16_t(-32768), sample));
else if constexpr (std::is_same_v<T, uint16_t>)
buf[f * this->channels() + out_ch] = std::min(uint16_t(65535), std::max(uint16_t(0), sample));
else
buf[f * this->channels() + out_ch] = sample;
x = sample::sadd(x, tmp[f * m_in->channels() + in_ch] * matrix[out_ch * m_in->channels() + in_ch]);

buf[f * this->channels() + out_ch] = x;
}
}



+ 1
- 1
t/Makefile Bestand weergeven

@@ -1,5 +1,5 @@

SRC = audio.cpp
SRC = test.cpp audio-convert.cpp audio-sadd.cpp

all: test



t/audio.cpp → t/audio-convert.cpp Bestand weergeven

@@ -1,4 +1,4 @@
#include <lol/lib/doctest_main>
#include <lol/lib/doctest>
#include <lol/audio/stream>

TEST_CASE("sample conversion: float|double ←→ float|double")

+ 182
- 0
t/audio-sadd.cpp Bestand weergeven

@@ -0,0 +1,182 @@
#include <lol/lib/doctest>
#include <lol/audio/stream>

TEST_CASE("sample saturated add: int8_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<int8_t>(-0x80, -0x80) == -0x80);
CHECK(lol::audio::sample::sadd<int8_t>(-0x41, -0x41) == -0x80);
CHECK(lol::audio::sample::sadd<int8_t>(-0x40, -0x41) == -0x80);

// Standard operating mode
CHECK(lol::audio::sample::sadd<int8_t>(-0x40, -0x40) == -0x80);
CHECK(lol::audio::sample::sadd<int8_t>(-0x3f, -0x3f) == -0x7e);
CHECK(lol::audio::sample::sadd<int8_t>(-0x01, -0x01) == -0x02);
CHECK(lol::audio::sample::sadd<int8_t>(-0x01, 0x00) == -0x01);
CHECK(lol::audio::sample::sadd<int8_t>( 0x00, 0x00) == 0x00);
CHECK(lol::audio::sample::sadd<int8_t>( 0x00, 0x01) == 0x01);
CHECK(lol::audio::sample::sadd<int8_t>( 0x01, 0x01) == 0x02);
CHECK(lol::audio::sample::sadd<int8_t>( 0x3f, 0x3f) == 0x7e);
CHECK(lol::audio::sample::sadd<int8_t>( 0x3f, 0x40) == 0x7f);

// Overflow
CHECK(lol::audio::sample::sadd<int8_t>( 0x40, 0x40) == 0x7f);
CHECK(lol::audio::sample::sadd<int8_t>( 0x7f, 0x7f) == 0x7f);
}

TEST_CASE("sample saturated add: uint8_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<uint8_t>(0x00, 0x00) == 0x00);
CHECK(lol::audio::sample::sadd<uint8_t>(0x3f, 0x3f) == 0x00);

// Standard operating mode
CHECK(lol::audio::sample::sadd<uint8_t>(0x40, 0x40) == 0x00);
CHECK(lol::audio::sample::sadd<uint8_t>(0x41, 0x41) == 0x02);
CHECK(lol::audio::sample::sadd<uint8_t>(0x7f, 0x7f) == 0x7e);
CHECK(lol::audio::sample::sadd<uint8_t>(0x7f, 0x80) == 0x7f);
CHECK(lol::audio::sample::sadd<uint8_t>(0x80, 0x80) == 0x80);
CHECK(lol::audio::sample::sadd<uint8_t>(0x80, 0x81) == 0x81);
CHECK(lol::audio::sample::sadd<uint8_t>(0x81, 0x81) == 0x82);
CHECK(lol::audio::sample::sadd<uint8_t>(0xbf, 0xbf) == 0xfe);
CHECK(lol::audio::sample::sadd<uint8_t>(0xbf, 0xc0) == 0xff);

// Overflow
CHECK(lol::audio::sample::sadd<uint8_t>(0xc0, 0xc0) == 0xff);
CHECK(lol::audio::sample::sadd<uint8_t>(0xff, 0xff) == 0xff);
}

TEST_CASE("sample saturated add: int16_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<int16_t>(-0x8000, -0x8000) == -0x8000);
CHECK(lol::audio::sample::sadd<int16_t>(-0x4001, -0x4001) == -0x8000);
CHECK(lol::audio::sample::sadd<int16_t>(-0x4000, -0x4001) == -0x8000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<int16_t>(-0x4000, -0x4000) == -0x8000);
CHECK(lol::audio::sample::sadd<int16_t>(-0x3fff, -0x3fff) == -0x7ffe);
CHECK(lol::audio::sample::sadd<int16_t>(-0x0001, -0x0001) == -0x0002);
CHECK(lol::audio::sample::sadd<int16_t>(-0x0001, 0x0000) == -0x0001);
CHECK(lol::audio::sample::sadd<int16_t>( 0x0000, 0x0000) == 0x0000);
CHECK(lol::audio::sample::sadd<int16_t>( 0x0000, 0x0001) == 0x0001);
CHECK(lol::audio::sample::sadd<int16_t>( 0x0001, 0x0001) == 0x0002);
CHECK(lol::audio::sample::sadd<int16_t>( 0x3fff, 0x3fff) == 0x7ffe);
CHECK(lol::audio::sample::sadd<int16_t>( 0x3fff, 0x4000) == 0x7fff);

// Overflow
CHECK(lol::audio::sample::sadd<int16_t>( 0x4000, 0x4000) == 0x7fff);
CHECK(lol::audio::sample::sadd<int16_t>( 0x7fff, 0x7fff) == 0x7fff);
}

TEST_CASE("sample saturated add: uint16_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<uint16_t>(0x0000, 0x0000) == 0x0000);
CHECK(lol::audio::sample::sadd<uint16_t>(0x3fff, 0x3fff) == 0x0000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<uint16_t>(0x4000, 0x4000) == 0x0000);
CHECK(lol::audio::sample::sadd<uint16_t>(0x4001, 0x4001) == 0x0002);
CHECK(lol::audio::sample::sadd<uint16_t>(0x7fff, 0x7fff) == 0x7ffe);
CHECK(lol::audio::sample::sadd<uint16_t>(0x7fff, 0x8000) == 0x7fff);
CHECK(lol::audio::sample::sadd<uint16_t>(0x8000, 0x8000) == 0x8000);
CHECK(lol::audio::sample::sadd<uint16_t>(0x8000, 0x8001) == 0x8001);
CHECK(lol::audio::sample::sadd<uint16_t>(0x8001, 0x8001) == 0x8002);
CHECK(lol::audio::sample::sadd<uint16_t>(0xbfff, 0xbfff) == 0xfffe);
CHECK(lol::audio::sample::sadd<uint16_t>(0xbfff, 0xc000) == 0xffff);

// Overflow
CHECK(lol::audio::sample::sadd<uint16_t>(0xc000, 0xc000) == 0xffff);
CHECK(lol::audio::sample::sadd<uint16_t>(0xffff, 0xffff) == 0xffff);
}

TEST_CASE("sample saturated add: uint32_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<uint32_t>(0x00000000, 0x00000000) == 0x00000000);
CHECK(lol::audio::sample::sadd<uint32_t>(0x3fffffff, 0x3fffffff) == 0x00000000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<uint32_t>(0x40000000, 0x40000000) == 0x00000000);
CHECK(lol::audio::sample::sadd<uint32_t>(0x40000001, 0x40000001) == 0x00000002);
CHECK(lol::audio::sample::sadd<uint32_t>(0x7fffffff, 0x7fffffff) == 0x7ffffffe);
CHECK(lol::audio::sample::sadd<uint32_t>(0x7fffffff, 0x80000000) == 0x7fffffff);
CHECK(lol::audio::sample::sadd<uint32_t>(0x80000000, 0x80000000) == 0x80000000);
CHECK(lol::audio::sample::sadd<uint32_t>(0x80000000, 0x80000001) == 0x80000001);
CHECK(lol::audio::sample::sadd<uint32_t>(0x80000001, 0x80000001) == 0x80000002);
CHECK(lol::audio::sample::sadd<uint32_t>(0xbfffffff, 0xbfffffff) == 0xfffffffe);
CHECK(lol::audio::sample::sadd<uint32_t>(0xbfffffff, 0xc0000000) == 0xffffffff);

// Overflow
CHECK(lol::audio::sample::sadd<uint32_t>(0xc0000000, 0xc0000000) == 0xffffffff);
CHECK(lol::audio::sample::sadd<uint32_t>(0xffffffff, 0xffffffff) == 0xffffffff);
}

TEST_CASE("sample saturated add: int32_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<int32_t>(-0x80000000, -0x80000000) == -0x80000000);
CHECK(lol::audio::sample::sadd<int32_t>(-0x40000001, -0x40000001) == -0x80000000);
CHECK(lol::audio::sample::sadd<int32_t>(-0x40000000, -0x40000001) == -0x80000000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<int32_t>(-0x40000000, -0x40000000) == -0x80000000);
CHECK(lol::audio::sample::sadd<int32_t>(-0x3fffffff, -0x3fffffff) == -0x7ffffffe);
CHECK(lol::audio::sample::sadd<int32_t>(-0x00000001, -0x00000001) == -0x00000002);
CHECK(lol::audio::sample::sadd<int32_t>(-0x00000001, 0x00000000) == -0x00000001);
CHECK(lol::audio::sample::sadd<int32_t>( 0x00000000, 0x00000000) == 0x00000000);
CHECK(lol::audio::sample::sadd<int32_t>( 0x00000000, 0x00000001) == 0x00000001);
CHECK(lol::audio::sample::sadd<int32_t>( 0x00000001, 0x00000001) == 0x00000002);
CHECK(lol::audio::sample::sadd<int32_t>( 0x3fffffff, 0x3fffffff) == 0x7ffffffe);
CHECK(lol::audio::sample::sadd<int32_t>( 0x3fffffff, 0x40000000) == 0x7fffffff);

// Overflow
CHECK(lol::audio::sample::sadd<int32_t>( 0x40000000, 0x40000000) == 0x7fffffff);
CHECK(lol::audio::sample::sadd<int32_t>( 0x7fffffff, 0x7fffffff) == 0x7fffffff);
}

TEST_CASE("sample saturated add: uint64_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<uint64_t>(0x0000000000000000, 0x0000000000000000) == 0x0000000000000000);
CHECK(lol::audio::sample::sadd<uint64_t>(0x3fffffffffffffff, 0x3fffffffffffffff) == 0x0000000000000000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<uint64_t>(0x4000000000000000, 0x4000000000000000) == 0x0000000000000000);
CHECK(lol::audio::sample::sadd<uint64_t>(0x4000000000000001, 0x4000000000000001) == 0x0000000000000002);
CHECK(lol::audio::sample::sadd<uint64_t>(0x7fffffffffffffff, 0x7fffffffffffffff) == 0x7ffffffffffffffe);
CHECK(lol::audio::sample::sadd<uint64_t>(0x7fffffffffffffff, 0x8000000000000000) == 0x7fffffffffffffff);
CHECK(lol::audio::sample::sadd<uint64_t>(0x8000000000000000, 0x8000000000000000) == 0x8000000000000000);
CHECK(lol::audio::sample::sadd<uint64_t>(0x8000000000000000, 0x8000000000000001) == 0x8000000000000001);
CHECK(lol::audio::sample::sadd<uint64_t>(0x8000000000000001, 0x8000000000000001) == 0x8000000000000002);
CHECK(lol::audio::sample::sadd<uint64_t>(0xbfffffffffffffff, 0xbfffffffffffffff) == 0xfffffffffffffffe);
CHECK(lol::audio::sample::sadd<uint64_t>(0xbfffffffffffffff, 0xc000000000000000) == 0xffffffffffffffff);

// Overflow
CHECK(lol::audio::sample::sadd<uint64_t>(0xc000000000000000, 0xc000000000000000) == 0xffffffffffffffff);
CHECK(lol::audio::sample::sadd<uint64_t>(0xffffffffffffffff, 0xffffffffffffffff) == 0xffffffffffffffff);
}

TEST_CASE("sample saturated add: int64_t")
{
// Underflow
CHECK(lol::audio::sample::sadd<int64_t>(-0x8000000000000000, -0x8000000000000000) == -0x8000000000000000);
CHECK(lol::audio::sample::sadd<int64_t>(-0x4000000000000001, -0x4000000000000001) == -0x8000000000000000);
CHECK(lol::audio::sample::sadd<int64_t>(-0x4000000000000000, -0x4000000000000001) == -0x8000000000000000);

// Standard operating mode
CHECK(lol::audio::sample::sadd<int64_t>(-0x4000000000000000, -0x4000000000000000) == -0x8000000000000000);
CHECK(lol::audio::sample::sadd<int64_t>(-0x3fffffffffffffff, -0x3fffffffffffffff) == -0x7ffffffffffffffe);
CHECK(lol::audio::sample::sadd<int64_t>(-0x0000000000000001, -0x0000000000000001) == -0x0000000000000002);
CHECK(lol::audio::sample::sadd<int64_t>(-0x0000000000000001, 0x0000000000000000) == -0x0000000000000001);
CHECK(lol::audio::sample::sadd<int64_t>( 0x0000000000000000, 0x0000000000000000) == 0x0000000000000000);
CHECK(lol::audio::sample::sadd<int64_t>( 0x0000000000000000, 0x0000000000000001) == 0x0000000000000001);
CHECK(lol::audio::sample::sadd<int64_t>( 0x0000000000000001, 0x0000000000000001) == 0x0000000000000002);
CHECK(lol::audio::sample::sadd<int64_t>( 0x3fffffffffffffff, 0x3fffffffffffffff) == 0x7ffffffffffffffe);
CHECK(lol::audio::sample::sadd<int64_t>( 0x3fffffffffffffff, 0x4000000000000000) == 0x7fffffffffffffff);

// Overflow
CHECK(lol::audio::sample::sadd<int64_t>( 0x4000000000000000, 0x4000000000000000) == 0x7fffffffffffffff);
CHECK(lol::audio::sample::sadd<int64_t>( 0x7fffffffffffffff, 0x7fffffffffffffff) == 0x7fffffffffffffff);
}

+ 1
- 0
t/test.cpp Bestand weergeven

@@ -0,0 +1 @@
#include <lol/lib/doctest_main>

Laden…
Annuleren
Opslaan