From 66fdc2e6eb222a29e87c2dca3fa16eb980e3c59a Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Fri, 25 Oct 2024 10:48:45 +0200 Subject: [PATCH] WIP --- .gitignore | 1 + include/lol/image/image | 18 ++ include/lol/image/kernel | 18 ++ include/lol/private/image/image.h | 16 -- include/lol/private/image/kernel.h | 59 +++++ include/lol/private/image/kernel.ipp | 357 +++++++++++++++++++++++++++ include/lol/private/signal/audio.h | 29 +++ include/lol/{image => signal/audio} | 4 +- t/Makefile | 13 +- t/base-narray.cpp | 18 ++ t/image-kernel.cpp | 188 ++++++++++++++ 11 files changed, 700 insertions(+), 21 deletions(-) create mode 100644 include/lol/image/image create mode 100644 include/lol/image/kernel create mode 100644 include/lol/private/image/kernel.h create mode 100644 include/lol/private/image/kernel.ipp create mode 100644 include/lol/private/signal/audio.h rename include/lol/{image => signal/audio} (82%) create mode 100644 t/base-narray.cpp create mode 100644 t/image-kernel.cpp diff --git a/.gitignore b/.gitignore index 72278f39..fd0dd05c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ .*.swp *~ # Binaries +*.o *.exe t/test diff --git a/include/lol/image/image b/include/lol/image/image new file mode 100644 index 00000000..9b645071 --- /dev/null +++ b/include/lol/image/image @@ -0,0 +1,18 @@ +// +// Lol Engine +// +// Copyright © 2010–2020 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#include "../private/push_macros.h" +#include "../private/image/image.h" +#include "../private/pop_macros.h" + diff --git a/include/lol/image/kernel b/include/lol/image/kernel new file mode 100644 index 00000000..8fe60978 --- /dev/null +++ b/include/lol/image/kernel @@ -0,0 +1,18 @@ +// +// Lol Engine +// +// Copyright © 2010–2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#include "../private/push_macros.h" +#include "../private/image/kernel.h" +#include "../private/pop_macros.h" + diff --git a/include/lol/private/image/image.h b/include/lol/private/image/image.h index 88b25a6f..cd30b711 100644 --- a/include/lol/private/image/image.h +++ b/include/lol/private/image/image.h @@ -142,20 +142,4 @@ enum class ResampleAlgorithm : uint8_t Bresenham, }; -enum class EdiffAlgorithm : uint8_t -{ - FloydSteinberg, - JaJuNi, - Atkinson, - Fan, - ShiauFan, - ShiauFan2, - Stucki, - Burkes, - Sierra, - Sierra2, - Lite, -}; - } // namespace lol - diff --git a/include/lol/private/image/kernel.h b/include/lol/private/image/kernel.h new file mode 100644 index 00000000..e3f5541d --- /dev/null +++ b/include/lol/private/image/kernel.h @@ -0,0 +1,59 @@ +// +// Lol Engine +// +// Copyright © 2010–2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +// +// The image class +// ——————————————— +// + +#include // lol::array2d +#include // lol::ivec2 + +namespace lol::image +{ + +enum class EdiffAlgorithm : uint8_t +{ + FloydSteinberg, + JaJuNi, + Atkinson, + Fan, + ShiauFan, + ShiauFan2, + Stucki, + Burkes, + Sierra, + Sierra2, + Lite, +}; + +struct kernel +{ + kernel() = delete; + + static array2d normalize(array2d const &kernel); + + static array2d bayer(ivec2 size); + static array2d halftone(ivec2 size); + static array2d blue_noise(ivec2 size, + ivec2 gsize = ivec2(7, 7)); + static array2d ediff(EdiffAlgorithm algorithm); + static array2d gaussian(vec2 radius, + float angle = 0.f, + vec2 delta = vec2(0.f, 0.f)); +}; + +} // namespace lol::image + +#include "kernel.ipp" diff --git a/include/lol/private/image/kernel.ipp b/include/lol/private/image/kernel.ipp new file mode 100644 index 00000000..9e51a1d1 --- /dev/null +++ b/include/lol/private/image/kernel.ipp @@ -0,0 +1,357 @@ +// +// Lol Engine +// +// Copyright © 2004–2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#include // std::cos, std::sin +#include // std::memset +#include "../math/rand.h" // lol::rand +#include // std::format + +/* + * Stock kernels + */ + +namespace lol +{ + +array2d image::kernel::bayer(ivec2 size) +{ + array2d ret(size); + + int n = 1; + while (n < size.x || n < size.y) + n *= 2; + + for (int j = 0; j < size.y; j++) + for (int i = 0; i < size.x; i++) + { + int x = 0; + + for (int k = 1, l = n * n / 4; k < n; k *= 2, l /= 4) + { + if ((i & k) && (j & k)) + x += l; + else if (i & k) + x += 3 * l; + else if (j & k) + x += 2 * l; + } + + ret(i, j) = (float)(x + 1) / (n * n + 1); + } + + return ret; +} + +array2d image::kernel::halftone(ivec2 size) +{ + static const float pi = std::acos(-1.f); + + array2d ret(size); + + for (int y = 0; y < size.y; y++) + for (int x = 0; x < size.x; x++) + { + float dx = 2.f * (x + 0.02f) / size.x - 0.5f; + float dy = 2.f * (y + 0.03f) / size.y - 0.5f; + bool flip = false; + if (dx > 0.5f) + { + flip = !flip; + dx -= 1.0f; + } + if (dy > 0.5f) + { + flip = !flip; + dy -= 1.0f; + } + /* Using dx²+dy² here creates another interesting halftone. */ + float r = - std::cos(pi * (dx - dy)) - std::cos(pi * (dx + dy)); + + ret(x, y) = flip ? 10.f - r : r; + } + + return normalize(ret); +} + +array2d image::kernel::blue_noise(ivec2 size, ivec2 gsize) +{ + float const epsilon = 1.f / (size.x * size.y + 1); + gsize = lol::min(size, gsize); + + array2d ret(size); + array2d dots(size); + + /* Create a small Gaussian kernel for filtering */ + array2d gaussian(gsize); + for (int j = 0; j < gsize.y; ++j) + for (int i = 0; i < gsize.x; ++i) + { + ivec2 const distance = gsize / 2 - ivec2(i, j); + gaussian(i, j) = std::exp(-lol::sqlength(distance) + / (0.05f * gsize.x * gsize.y)); + } + + /* Helper function to find voids and clusters */ + auto setdot = [&] (ivec2 pos, float val) + { + float const delta = val - dots(pos)[0]; + dots(pos)[0] = val; + + for (int j = 0; j < gsize.y; ++j) + for (int i = 0; i < gsize.x; ++i) + dots((pos.x + i - gsize.x / 2 + size.x) % size.x, + (pos.y + j - gsize.y / 2 + size.y) % size.y) + [1] += gaussian(i, j) * delta; + }; + + auto best = [&] (float val, float mul) -> ivec2 + { + float maxval = -(float)(size.x * size.y); + ivec2 coord(0, 0); + for (int y = 0; y < size.y; ++y) + for (int x = 0; x < size.x; ++x) + { + if (dots(x, y)[0] != val) + continue; + + float total = dots(x, y)[1]; + if (total * mul > maxval) + { + maxval = total * mul; + coord = ivec2(x, y); + } + } + + return coord; + }; + + /* Generate an array with about 10% random dots */ + int const ndots = (size.x * size.y + 9) / 10; + std::memset((void *)dots.data(), 0, dots.bytes()); + for (int n = 0; n < ndots; ) + { + ivec2 pos(lol::rand(size.x), lol::rand(size.y)); + if (dots(pos)[0]) + continue; + setdot(ivec2(pos), 1.0f); + ++n; + } + + /* Rearrange 1s so that they occupy the largest voids */ + for (;;) + { + ivec2 bestcluster = best(1.0f, 1.0f); + setdot(bestcluster, 0.0f); + ivec2 bestvoid = best(0.0f, -1.0f); + setdot(bestvoid, 1.0f); + if (bestcluster == bestvoid) + break; + } + + /* Reorder all 1s and replace them with 0.0001 */ + for (int n = ndots; n--; ) + { + ivec2 bestcluster = best(1.0f, 1.0f); + ret(bestcluster) = (n + 1.0f) * epsilon; + setdot(bestcluster, 0.0001f); + } + + /* Reorder all 0s and replace them with 0.0001 */ + for (int n = ndots; n < size.x * size.y; ++n) + { + ivec2 bestvoid = best(0.0f, -1.0f); + ret(bestvoid) = (n + 1.0f) * epsilon; + setdot(bestvoid, 0.0001f); + } + + return ret; +} + +struct Dot +{ + int x, y; + float val; +}; + +static int cmpdot(const void *p1, const void *p2) +{ + return ((Dot const *)p1)->val > ((Dot const *)p2)->val; +} + +array2d image::kernel::normalize(array2d const &kernel) +{ + ivec2 size = kernel.sizes(); + + std::vector tmp; + tmp.resize(size.x * size.y); + + for (int y = 0; y < size.y; y++) + for (int x = 0; x < size.x; x++) + { + tmp[y * size.x + x].x = x; + tmp[y * size.x + x].y = y; + tmp[y * size.x + x].val = kernel(y, x); + } + // FIXME: get rid of qsort and use proper C++ algorithms + std::qsort(tmp.data(), size.x * size.y, sizeof(Dot), cmpdot); + + array2d dst(size); + + float const epsilon = 1.f / (size.x * size.y + 1); + for (int n = 0; n < size.x * size.y; n++) + { + int x = tmp[n].x; + int y = tmp[n].y; + dst(x, y) = (n + 1.f) * epsilon; + } + + return dst; +} + +array2d image::kernel::ediff(EdiffAlgorithm algorithm) +{ + switch (algorithm) + { + case EdiffAlgorithm::FloydSteinberg: + return { { 0.f, 1.f, 7.f/16, }, + { 3.f/16, 5.f/16, 1.f/16, }, }; + + case EdiffAlgorithm::JaJuNi: + return { { 0.f, 0.f, 1.f, 7.f/48, 5.f/48, }, + { 3.f/48, 5.f/48, 7.f/48, 5.f/48, 3.f/48, }, + { 1.f/48, 3.f/48, 5.f/48, 3.f/48, 1.f/48, }, }; + + case EdiffAlgorithm::Atkinson: + return { { 0.f, 1.f, 1.f/8, 1.f/8, }, + { 1.f/8, 1.f/8, 1.f/8, 0.f, }, + { 0.f, 1.f/8, 0.f, 0.f, }, }; + + case EdiffAlgorithm::Fan: + return { { 0.f, 0.f, 1.f, 7.f/16, }, + { 1.f/16, 3.f/16, 5.f/16, 0.f, }, }; + + case EdiffAlgorithm::ShiauFan: + return { { 0.f, 0.f, 1.f, 1.f/2, }, + { 1.f/8, 1.f/8, 1.f/4, 0.f, }, }; + + case EdiffAlgorithm::ShiauFan2: + return { { 0.f, 0.f, 0.f, 1.f, 1.f/2, }, + { 1.f/16, 1.f/16, 1.f/8, 1.f/4, 0.f, }, }; + + case EdiffAlgorithm::Stucki: + return { { 0.f, 0.f, 1.f, 8.f/42, 4.f/42, }, + { 2.f/42, 4.f/42, 8.f/42, 4.f/42, 2.f/42, }, + { 1.f/42, 2.f/42, 4.f/42, 2.f/42, 1.f/42, }, }; + + case EdiffAlgorithm::Burkes: + return { { 0.f, 0.f, 1.f, 4.f/16, 2.f/16, }, + { 1.f/16, 2.f/16, 4.f/16, 2.f/16, 1.f/16, }, }; + + case EdiffAlgorithm::Sierra: + return { { 0.f, 0.f, 1.f, 5.f/32, 3.f/32, }, + { 2.f/32, 4.f/32, 5.f/32, 4.f/32, 2.f/32, }, + { 0.f, 2.f/32, 3.f/32, 2.f/32, 0.f, }, }; + + case EdiffAlgorithm::Sierra2: + return { { 0.f, 0.f, 1.f, 4.f/16, 3.f/16, }, + { 1.f/16, 2.f/16, 3.f/16, 2.f/16, 1.f/16, }, }; + + case EdiffAlgorithm::Lite: + return { { 0.f, 1.f, 1.f/2, }, + { 1.f/4, 1.f/4, 0.f, }, }; + } + + return { { 1.f } }; +} + +/* Any standard deviation below this value will be rounded up, in order + * to avoid ridiculously low values. exp(-1/(2*0.2*0.2)) is < 10^-5 so + * there is little chance that any value below 0.2 will be useful. */ +#define BLUR_EPSILON 0.2f + +array2d image::kernel::gaussian(vec2 radius, float angle, vec2 delta) +{ + array2d kernel; + + if (radius.x < BLUR_EPSILON) + radius.x = BLUR_EPSILON; + if (radius.y < BLUR_EPSILON) + radius.y = BLUR_EPSILON; + + float const sint = std::sin(angle); + float const cost = std::cos(angle); + + // Compute the final ellipse's bounding box + float const bbx = std::sqrt(std::pow(radius.x * cost, 2.f) + std::pow(radius.y * sint, 2.f)); + float const bby = std::sqrt(std::pow(radius.y * cost, 2.f) + std::pow(radius.x * sint, 2.f)); + + /* FIXME: the kernel becomes far too big with large values of dx, because + * we grow both left and right. Fix the growing direction. */ + int const krx = (int)(3.f * bbx + .99999f + std::ceil(std::fabs(delta.x))); + int const kry = (int)(3.f * bby + .99999f + std::ceil(std::fabs(delta.y))); + ivec2 size(2 * krx + 1, 2 * kry + 1); + float const Kx = -1.f / (2.f * radius.x * radius.x); + float const Ky = -1.f / (2.f * radius.y * radius.y); + + kernel.resize(size); + + float t = 0.f; + + for (int j = -kry; j <= kry; j++) + { + for (int i = -krx; i <= krx; i++) + { + /* FIXME: this level of interpolation sucks. We should + * interpolate on the full NxN grid for better quality. */ + static vec3 const samples[] = + { + vec3( 0.0f, 0.0f, 1.0f), + vec3(-0.4f, -0.4f, 0.8f), + vec3(-0.3f, 0.0f, 0.9f), + vec3(-0.4f, 0.4f, 0.8f), + vec3( 0.0f, 0.3f, 0.9f), + vec3( 0.4f, 0.4f, 0.8f), + vec3( 0.3f, 0.0f, 0.9f), + vec3( 0.4f, -0.4f, 0.8f), + vec3( 0.0f, -0.3f, 0.9f), + }; + + float d = 0.f; + + for (auto p : samples) + { + float u = (i + p.x) * cost - (j + p.y) * sint + delta.x; + float v = (i + p.x) * sint + (j + p.y) * cost + delta.y; + float ex = Kx * u * u; + float ey = Ky * v * v; + d += p.z * std::exp(ex + ey); + + /* Do not interpolate if this is a standard gaussian. */ + if (!delta.x && !delta.y && !angle) + break; + } + + kernel(i + krx, j + kry) = d; + t += d; + } + } + + for (int j = 0; j < size.y; j++) + for (int i = 0; i < size.x; i++) + kernel(i, j) *= (1.f / t); + + return kernel; +} + +} // namespace lol diff --git a/include/lol/private/signal/audio.h b/include/lol/private/signal/audio.h new file mode 100644 index 00000000..b7650d82 --- /dev/null +++ b/include/lol/private/signal/audio.h @@ -0,0 +1,29 @@ +// +// Lol Engine +// +// Copyright © 2010—2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +// +// The audio classes +// ————————————————— +// + +namespace lol +{ + +template +class converter +{ +public: + +protected: +}; diff --git a/include/lol/image b/include/lol/signal/audio similarity index 82% rename from include/lol/image rename to include/lol/signal/audio index f72ad3d3..58514667 100644 --- a/include/lol/image +++ b/include/lol/signal/audio @@ -1,7 +1,7 @@ // // Lol Engine // -// Copyright © 2010—2020 Sam Hocevar +// Copyright © 2010—2024 Sam Hocevar // // Lol Engine is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it @@ -13,6 +13,6 @@ #pragma once #include "private/push_macros.h" -#include "private/image/image.h" +#include "private/signal/audio.h" #include "private/pop_macros.h" diff --git a/t/Makefile b/t/Makefile index ced36fda..41b375e2 100644 --- a/t/Makefile +++ b/t/Makefile @@ -1,5 +1,9 @@ -SRC = test.cpp audio-convert.cpp audio-sadd.cpp +SRC = \ + test.cpp \ + audio-convert.cpp audio-sadd.cpp \ + base-narray.cpp \ + image-kernel.cpp all: test @@ -9,5 +13,8 @@ clean: check: test ./test -test: $(SRC) - $(CXX) -I../include $^ -o $@ +test: $(SRC:%.cpp=%.o) + $(CXX) $^ -o $@ + +%.o: %.cpp + $(CXX) -std=c++23 -I../include -c $^ -o $@ diff --git a/t/base-narray.cpp b/t/base-narray.cpp new file mode 100644 index 00000000..442ae744 --- /dev/null +++ b/t/base-narray.cpp @@ -0,0 +1,18 @@ +#include +#include + +TEST_CASE("array2d") +{ + auto a = lol::array2d(3, 2); + + for (int n = 0; n < 6; ++n) + a[n] = n; + + CHECK(a(0, 0) == 0); + CHECK(a(1, 0) == 1); + CHECK(a(2, 0) == 2); + + CHECK(a(0, 1) == 3); + CHECK(a(1, 1) == 4); + CHECK(a(2, 1) == 5); +} diff --git a/t/image-kernel.cpp b/t/image-kernel.cpp new file mode 100644 index 00000000..4255d44c --- /dev/null +++ b/t/image-kernel.cpp @@ -0,0 +1,188 @@ +#include + +TEST_CASE("bayer kernel") +{ + auto &k = image::kernel::bayer({4, 4}); + +} + +/* + CHECK + // Underflow + CHECK(lol::audio::sample::sadd(-0x80, -0x80) == -0x80); + CHECK(lol::audio::sample::sadd(-0x41, -0x41) == -0x80); + CHECK(lol::audio::sample::sadd(-0x40, -0x41) == -0x80); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(-0x40, -0x40) == -0x80); + CHECK(lol::audio::sample::sadd(-0x3f, -0x3f) == -0x7e); + CHECK(lol::audio::sample::sadd(-0x01, -0x01) == -0x02); + CHECK(lol::audio::sample::sadd(-0x01, 0x00) == -0x01); + CHECK(lol::audio::sample::sadd( 0x00, 0x00) == 0x00); + CHECK(lol::audio::sample::sadd( 0x00, 0x01) == 0x01); + CHECK(lol::audio::sample::sadd( 0x01, 0x01) == 0x02); + CHECK(lol::audio::sample::sadd( 0x3f, 0x3f) == 0x7e); + CHECK(lol::audio::sample::sadd( 0x3f, 0x40) == 0x7f); + + // Overflow + CHECK(lol::audio::sample::sadd( 0x40, 0x40) == 0x7f); + CHECK(lol::audio::sample::sadd( 0x7f, 0x7f) == 0x7f); +} + +TEST_CASE("sample saturated add: uint8_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(0x00, 0x00) == 0x00); + CHECK(lol::audio::sample::sadd(0x3f, 0x3f) == 0x00); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(0x40, 0x40) == 0x00); + CHECK(lol::audio::sample::sadd(0x41, 0x41) == 0x02); + CHECK(lol::audio::sample::sadd(0x7f, 0x7f) == 0x7e); + CHECK(lol::audio::sample::sadd(0x7f, 0x80) == 0x7f); + CHECK(lol::audio::sample::sadd(0x80, 0x80) == 0x80); + CHECK(lol::audio::sample::sadd(0x80, 0x81) == 0x81); + CHECK(lol::audio::sample::sadd(0x81, 0x81) == 0x82); + CHECK(lol::audio::sample::sadd(0xbf, 0xbf) == 0xfe); + CHECK(lol::audio::sample::sadd(0xbf, 0xc0) == 0xff); + + // Overflow + CHECK(lol::audio::sample::sadd(0xc0, 0xc0) == 0xff); + CHECK(lol::audio::sample::sadd(0xff, 0xff) == 0xff); +} + +TEST_CASE("sample saturated add: int16_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(-0x8000, -0x8000) == -0x8000); + CHECK(lol::audio::sample::sadd(-0x4001, -0x4001) == -0x8000); + CHECK(lol::audio::sample::sadd(-0x4000, -0x4001) == -0x8000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(-0x4000, -0x4000) == -0x8000); + CHECK(lol::audio::sample::sadd(-0x3fff, -0x3fff) == -0x7ffe); + CHECK(lol::audio::sample::sadd(-0x0001, -0x0001) == -0x0002); + CHECK(lol::audio::sample::sadd(-0x0001, 0x0000) == -0x0001); + CHECK(lol::audio::sample::sadd( 0x0000, 0x0000) == 0x0000); + CHECK(lol::audio::sample::sadd( 0x0000, 0x0001) == 0x0001); + CHECK(lol::audio::sample::sadd( 0x0001, 0x0001) == 0x0002); + CHECK(lol::audio::sample::sadd( 0x3fff, 0x3fff) == 0x7ffe); + CHECK(lol::audio::sample::sadd( 0x3fff, 0x4000) == 0x7fff); + + // Overflow + CHECK(lol::audio::sample::sadd( 0x4000, 0x4000) == 0x7fff); + CHECK(lol::audio::sample::sadd( 0x7fff, 0x7fff) == 0x7fff); +} + +TEST_CASE("sample saturated add: uint16_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(0x0000, 0x0000) == 0x0000); + CHECK(lol::audio::sample::sadd(0x3fff, 0x3fff) == 0x0000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(0x4000, 0x4000) == 0x0000); + CHECK(lol::audio::sample::sadd(0x4001, 0x4001) == 0x0002); + CHECK(lol::audio::sample::sadd(0x7fff, 0x7fff) == 0x7ffe); + CHECK(lol::audio::sample::sadd(0x7fff, 0x8000) == 0x7fff); + CHECK(lol::audio::sample::sadd(0x8000, 0x8000) == 0x8000); + CHECK(lol::audio::sample::sadd(0x8000, 0x8001) == 0x8001); + CHECK(lol::audio::sample::sadd(0x8001, 0x8001) == 0x8002); + CHECK(lol::audio::sample::sadd(0xbfff, 0xbfff) == 0xfffe); + CHECK(lol::audio::sample::sadd(0xbfff, 0xc000) == 0xffff); + + // Overflow + CHECK(lol::audio::sample::sadd(0xc000, 0xc000) == 0xffff); + CHECK(lol::audio::sample::sadd(0xffff, 0xffff) == 0xffff); +} + +TEST_CASE("sample saturated add: uint32_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(0x00000000, 0x00000000) == 0x00000000); + CHECK(lol::audio::sample::sadd(0x3fffffff, 0x3fffffff) == 0x00000000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(0x40000000, 0x40000000) == 0x00000000); + CHECK(lol::audio::sample::sadd(0x40000001, 0x40000001) == 0x00000002); + CHECK(lol::audio::sample::sadd(0x7fffffff, 0x7fffffff) == 0x7ffffffe); + CHECK(lol::audio::sample::sadd(0x7fffffff, 0x80000000) == 0x7fffffff); + CHECK(lol::audio::sample::sadd(0x80000000, 0x80000000) == 0x80000000); + CHECK(lol::audio::sample::sadd(0x80000000, 0x80000001) == 0x80000001); + CHECK(lol::audio::sample::sadd(0x80000001, 0x80000001) == 0x80000002); + CHECK(lol::audio::sample::sadd(0xbfffffff, 0xbfffffff) == 0xfffffffe); + CHECK(lol::audio::sample::sadd(0xbfffffff, 0xc0000000) == 0xffffffff); + + // Overflow + CHECK(lol::audio::sample::sadd(0xc0000000, 0xc0000000) == 0xffffffff); + CHECK(lol::audio::sample::sadd(0xffffffff, 0xffffffff) == 0xffffffff); +} + +TEST_CASE("sample saturated add: int32_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(-0x80000000, -0x80000000) == -0x80000000); + CHECK(lol::audio::sample::sadd(-0x40000001, -0x40000001) == -0x80000000); + CHECK(lol::audio::sample::sadd(-0x40000000, -0x40000001) == -0x80000000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(-0x40000000, -0x40000000) == -0x80000000); + CHECK(lol::audio::sample::sadd(-0x3fffffff, -0x3fffffff) == -0x7ffffffe); + CHECK(lol::audio::sample::sadd(-0x00000001, -0x00000001) == -0x00000002); + CHECK(lol::audio::sample::sadd(-0x00000001, 0x00000000) == -0x00000001); + CHECK(lol::audio::sample::sadd( 0x00000000, 0x00000000) == 0x00000000); + CHECK(lol::audio::sample::sadd( 0x00000000, 0x00000001) == 0x00000001); + CHECK(lol::audio::sample::sadd( 0x00000001, 0x00000001) == 0x00000002); + CHECK(lol::audio::sample::sadd( 0x3fffffff, 0x3fffffff) == 0x7ffffffe); + CHECK(lol::audio::sample::sadd( 0x3fffffff, 0x40000000) == 0x7fffffff); + + // Overflow + CHECK(lol::audio::sample::sadd( 0x40000000, 0x40000000) == 0x7fffffff); + CHECK(lol::audio::sample::sadd( 0x7fffffff, 0x7fffffff) == 0x7fffffff); +} + +TEST_CASE("sample saturated add: uint64_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(0x0000000000000000, 0x0000000000000000) == 0x0000000000000000); + CHECK(lol::audio::sample::sadd(0x3fffffffffffffff, 0x3fffffffffffffff) == 0x0000000000000000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(0x4000000000000000, 0x4000000000000000) == 0x0000000000000000); + CHECK(lol::audio::sample::sadd(0x4000000000000001, 0x4000000000000001) == 0x0000000000000002); + CHECK(lol::audio::sample::sadd(0x7fffffffffffffff, 0x7fffffffffffffff) == 0x7ffffffffffffffe); + CHECK(lol::audio::sample::sadd(0x7fffffffffffffff, 0x8000000000000000) == 0x7fffffffffffffff); + CHECK(lol::audio::sample::sadd(0x8000000000000000, 0x8000000000000000) == 0x8000000000000000); + CHECK(lol::audio::sample::sadd(0x8000000000000000, 0x8000000000000001) == 0x8000000000000001); + CHECK(lol::audio::sample::sadd(0x8000000000000001, 0x8000000000000001) == 0x8000000000000002); + CHECK(lol::audio::sample::sadd(0xbfffffffffffffff, 0xbfffffffffffffff) == 0xfffffffffffffffe); + CHECK(lol::audio::sample::sadd(0xbfffffffffffffff, 0xc000000000000000) == 0xffffffffffffffff); + + // Overflow + CHECK(lol::audio::sample::sadd(0xc000000000000000, 0xc000000000000000) == 0xffffffffffffffff); + CHECK(lol::audio::sample::sadd(0xffffffffffffffff, 0xffffffffffffffff) == 0xffffffffffffffff); +} + +TEST_CASE("sample saturated add: int64_t") +{ + // Underflow + CHECK(lol::audio::sample::sadd(-0x8000000000000000, -0x8000000000000000) == -0x8000000000000000); + CHECK(lol::audio::sample::sadd(-0x4000000000000001, -0x4000000000000001) == -0x8000000000000000); + CHECK(lol::audio::sample::sadd(-0x4000000000000000, -0x4000000000000001) == -0x8000000000000000); + + // Standard operating mode + CHECK(lol::audio::sample::sadd(-0x4000000000000000, -0x4000000000000000) == -0x8000000000000000); + CHECK(lol::audio::sample::sadd(-0x3fffffffffffffff, -0x3fffffffffffffff) == -0x7ffffffffffffffe); + CHECK(lol::audio::sample::sadd(-0x0000000000000001, -0x0000000000000001) == -0x0000000000000002); + CHECK(lol::audio::sample::sadd(-0x0000000000000001, 0x0000000000000000) == -0x0000000000000001); + CHECK(lol::audio::sample::sadd( 0x0000000000000000, 0x0000000000000000) == 0x0000000000000000); + CHECK(lol::audio::sample::sadd( 0x0000000000000000, 0x0000000000000001) == 0x0000000000000001); + CHECK(lol::audio::sample::sadd( 0x0000000000000001, 0x0000000000000001) == 0x0000000000000002); + CHECK(lol::audio::sample::sadd( 0x3fffffffffffffff, 0x3fffffffffffffff) == 0x7ffffffffffffffe); + CHECK(lol::audio::sample::sadd( 0x3fffffffffffffff, 0x4000000000000000) == 0x7fffffffffffffff); + + // Overflow + CHECK(lol::audio::sample::sadd( 0x4000000000000000, 0x4000000000000000) == 0x7fffffffffffffff); + CHECK(lol::audio::sample::sadd( 0x7fffffffffffffff, 0x7fffffffffffffff) == 0x7fffffffffffffff); +} +*/