@@ -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); | |||
} | |||
*/ |