| @@ -2,5 +2,6 @@ | |||
| .*.swp | |||
| *~ | |||
| # Binaries | |||
| *.o | |||
| *.exe | |||
| t/test | |||
| @@ -0,0 +1,18 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2010–2020 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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" | |||
| @@ -0,0 +1,18 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2010–2024 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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" | |||
| @@ -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 | |||
| @@ -0,0 +1,59 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2010–2024 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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/narray> // lol::array2d | |||
| #include <lol/vector> // 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<float> normalize(array2d<float> const &kernel); | |||
| static array2d<float> bayer(ivec2 size); | |||
| static array2d<float> halftone(ivec2 size); | |||
| static array2d<float> blue_noise(ivec2 size, | |||
| ivec2 gsize = ivec2(7, 7)); | |||
| static array2d<float> ediff(EdiffAlgorithm algorithm); | |||
| static array2d<float> gaussian(vec2 radius, | |||
| float angle = 0.f, | |||
| vec2 delta = vec2(0.f, 0.f)); | |||
| }; | |||
| } // namespace lol::image | |||
| #include "kernel.ipp" | |||
| @@ -0,0 +1,357 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2004–2024 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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 <cmath> // std::cos, std::sin | |||
| #include <cstring> // std::memset | |||
| #include "../math/rand.h" // lol::rand | |||
| #include <lol/format> // std::format | |||
| /* | |||
| * Stock kernels | |||
| */ | |||
| namespace lol | |||
| { | |||
| array2d<float> image::kernel::bayer(ivec2 size) | |||
| { | |||
| array2d<float> 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<float> image::kernel::halftone(ivec2 size) | |||
| { | |||
| static const float pi = std::acos(-1.f); | |||
| array2d<float> 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<float> image::kernel::blue_noise(ivec2 size, ivec2 gsize) | |||
| { | |||
| float const epsilon = 1.f / (size.x * size.y + 1); | |||
| gsize = lol::min(size, gsize); | |||
| array2d<float> ret(size); | |||
| array2d<vec2> dots(size); | |||
| /* Create a small Gaussian kernel for filtering */ | |||
| array2d<float> 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<float> image::kernel::normalize(array2d<float> const &kernel) | |||
| { | |||
| ivec2 size = kernel.sizes(); | |||
| std::vector<Dot> 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<float> 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<float> 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<float> image::kernel::gaussian(vec2 radius, float angle, vec2 delta) | |||
| { | |||
| array2d<float> 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 | |||
| @@ -0,0 +1,29 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2010—2024 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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<typename FROM, typename TO> | |||
| class converter | |||
| { | |||
| public: | |||
| protected: | |||
| }; | |||
| @@ -1,7 +1,7 @@ | |||
| // | |||
| // Lol Engine | |||
| // | |||
| // Copyright © 2010—2020 Sam Hocevar <sam@hocevar.net> | |||
| // Copyright © 2010—2024 Sam Hocevar <sam@hocevar.net> | |||
| // | |||
| // 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" | |||
| @@ -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 $@ | |||
| @@ -0,0 +1,18 @@ | |||
| #include <lol/lib/doctest> | |||
| #include <lol/narray> | |||
| TEST_CASE("array2d") | |||
| { | |||
| auto a = lol::array2d<int>(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); | |||
| } | |||
| @@ -0,0 +1,188 @@ | |||
| #include <lol/image/kernel> | |||
| TEST_CASE("bayer kernel") | |||
| { | |||
| auto &k = image::kernel::bayer({4, 4}); | |||
| } | |||
| /* | |||
| CHECK | |||
| // 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); | |||
| } | |||
| */ | |||