Pārlūkot izejas kodu

audio: implement sample conversions between integer types

wip/image-kernel
Sam Hocevar pirms 9 mēnešiem
vecāks
revīzija
faad9b8fe5
2 mainītis faili ar 117 papildinājumiem un 53 dzēšanām
  1. +27
    -5
      include/lol/private/audio/stream.h
  2. +90
    -48
      t/audio.cpp

+ 27
- 5
include/lol/private/audio/stream.h Parādīt failu

@@ -35,9 +35,7 @@ public:
static inline TO convert(FROM x)
{
constexpr auto from_fp = std::is_floating_point_v<FROM>;
constexpr auto from_signed = !from_fp && std::is_signed_v<FROM>;
constexpr auto to_fp = std::is_floating_point_v<TO>;
constexpr auto to_signed = !to_fp && std::is_signed_v<TO>;

if constexpr (std::is_same_v<FROM, TO> || (from_fp && to_fp))
{
@@ -47,7 +45,7 @@ public:
else if constexpr (from_fp)
{
// From floating point to integer:
// - renormalise to 0…1
// - change range from -1…1 to 0…1
// - multiply by the size of the integer range
// - add min, round down, and clamp to min…max
FROM constexpr min(std::numeric_limits<TO>::min());
@@ -57,14 +55,38 @@ public:
}
else if constexpr (to_fp)
{
// From integer to floating point:
// - compute (x - min) / (max - min)
// - change range from 0…1 to -1…1
TO constexpr min(std::numeric_limits<FROM>::min());
TO constexpr max(std::numeric_limits<FROM>::max());
return (TO(x) - min) * 2 / (max - min) - 1;
}
else
{
// FIXME: this is better than nothing but we need a better implementation
return convert<double, TO>(convert<FROM, double>(x));
using UFROM = std::make_unsigned_t<FROM>;
using UTO = std::make_unsigned_t<TO>;

if constexpr (sizeof(FROM) > sizeof(TO))
{
// From a larger integer type to a smaller integer type:
// - convert to unsigned
// - shift right
// - convert back to signed if necessary
UFROM constexpr m = UFROM(1) << (8 * (sizeof(FROM) - sizeof(TO)));
UFROM tmp = UFROM(UFROM(x) - UFROM(std::numeric_limits<FROM>::min())) / m;
return TO(UTO(tmp) + UTO(std::numeric_limits<TO>::min()));
}
else
{
// From a smaller integer type to a larger integer type:
// - convert to unsigned
// - multiply by a magic constant such as 0x01010101 to propagate bytes
// - convert back to signed if necessary
UTO constexpr m = std::numeric_limits<UTO>::max() / std::numeric_limits<UFROM>::max();
UTO tmp = UFROM(UFROM(x) - UFROM(std::numeric_limits<FROM>::min())) * m;
return TO(UTO(tmp) + UTO(std::numeric_limits<TO>::min()));
}
}
}
};


+ 90
- 48
t/audio.cpp Parādīt failu

@@ -27,81 +27,123 @@ TEST_CASE("sample conversion between floating point types")
TEST_CASE("sample conversion from float to 8-bit")
{
auto cv1 = lol::audio::sample::convert<float, int8_t>;
CHECK(cv1(-1.5f) == -128);
CHECK(cv1(-1.0f) == -128);
CHECK(cv1(-0.5f) == -64);
CHECK(cv1( 0.0f) == 0);
CHECK(cv1( 0.5f) == 64);
CHECK(cv1( 1.0f) == 127);
CHECK(cv1( 1.5f) == 127);
CHECK(cv1(-1.5f) == -0x80);
CHECK(cv1(-1.0f) == -0x80);
CHECK(cv1(-0.5f) == -0x40);
CHECK(cv1( 0.0f) == 0x00);
CHECK(cv1( 0.5f) == 0x40);
CHECK(cv1( 1.0f) == 0x7f);
CHECK(cv1( 1.5f) == 0x7f);

auto cv2 = lol::audio::sample::convert<float, uint8_t>;
CHECK(cv2(-1.5f) == 0);
CHECK(cv2(-1.0f) == 0);
CHECK(cv2(-0.5f) == 64);
CHECK(cv2( 0.0f) == 128);
CHECK(cv2( 0.5f) == 192);
CHECK(cv2( 1.0f) == 255);
CHECK(cv2( 1.5f) == 255);
CHECK(cv2(-1.5f) == 0x00);
CHECK(cv2(-1.0f) == 0x00);
CHECK(cv2(-0.5f) == 0x40);
CHECK(cv2( 0.0f) == 0x80);
CHECK(cv2( 0.5f) == 0xc0);
CHECK(cv2( 1.0f) == 0xff);
CHECK(cv2( 1.5f) == 0xff);
}

TEST_CASE("sample conversion from 8-bit to float")
{
auto cv1 = lol::audio::sample::convert<int8_t, float>;
CHECK(cv1(-128) == -1.0f);
CHECK(cv1( 127) == 1.0f);
CHECK(cv1(-0x80) == -1.0f);
CHECK(cv1( 0x7f) == 1.0f);

auto cv2 = lol::audio::sample::convert<uint8_t, float>;
CHECK(cv2( 0) == -1.0f);
CHECK(cv2(255) == 1.0f);
CHECK(cv2(0x00) == -1.0f);
CHECK(cv2(0xff) == 1.0f);
}

TEST_CASE("sample conversion from float to 16-bit")
{
auto cv1 = lol::audio::sample::convert<float, int16_t>;
CHECK(cv1(-1.5f) == -32768);
CHECK(cv1(-1.0f) == -32768);
CHECK(cv1(-0.5f) == -16384);
CHECK(cv1( 0.0f) == 0);
CHECK(cv1( 0.5f) == 16384);
CHECK(cv1( 1.0f) == 32767);
CHECK(cv1( 1.5f) == 32767);
CHECK(cv1(-1.5f) == -0x8000);
CHECK(cv1(-1.0f) == -0x8000);
CHECK(cv1(-0.5f) == -0x4000);
CHECK(cv1( 0.0f) == 0x0000);
CHECK(cv1( 0.5f) == 0x4000);
CHECK(cv1( 1.0f) == 0x7fff);
CHECK(cv1( 1.5f) == 0x7fff);

auto cv2 = lol::audio::sample::convert<float, uint16_t>;
CHECK(cv2(-1.5f) == 0);
CHECK(cv2(-1.0f) == 0);
CHECK(cv2(-0.5f) == 16384);
CHECK(cv2( 0.0f) == 32768);
CHECK(cv2( 0.5f) == 49152);
CHECK(cv2( 1.0f) == 65535);
CHECK(cv2( 1.5f) == 65535);
CHECK(cv2(-1.5f) == 0x0000);
CHECK(cv2(-1.0f) == 0x0000);
CHECK(cv2(-0.5f) == 0x4000);
CHECK(cv2( 0.0f) == 0x8000);
CHECK(cv2( 0.5f) == 0xc000);
CHECK(cv2( 1.0f) == 0xffff);
CHECK(cv2( 1.5f) == 0xffff);
}

TEST_CASE("sample conversion from 16-bit to float")
{
auto cv1 = lol::audio::sample::convert<int16_t, float>;
CHECK(cv1(-32768) == -1.0f);
CHECK(cv1( 32767) == 1.0f);
CHECK(cv1(-0x8000) == -1.0f);
CHECK(cv1( 0x7fff) == 1.0f);

auto cv2 = lol::audio::sample::convert<uint16_t, float>;
CHECK(cv2( 0) == -1.0f);
CHECK(cv2(65535) == 1.0f);
CHECK(cv2(0x0000) == -1.0f);
CHECK(cv2(0xffff) == 1.0f);
}

TEST_CASE("sample conversion between signed and unsigned 8-bit")
{
auto cv1 = lol::audio::sample::convert<int8_t, uint8_t>;
CHECK(cv1(-0x80) == 0x00);
CHECK(cv1(-0x40) == 0x40);
CHECK(cv1(-0x01) == 0x7f);
CHECK(cv1( 0x00) == 0x80);
CHECK(cv1( 0x3f) == 0xbf);
CHECK(cv1( 0x7f) == 0xff);

auto cv2 = lol::audio::sample::convert<uint8_t, int8_t>;
CHECK(cv2(0x00) == -0x80);
CHECK(cv2(0x40) == -0x40);
CHECK(cv2(0x7f) == -0x01);
CHECK(cv2(0x80) == 0x00);
CHECK(cv2(0xbf) == 0x3f);
CHECK(cv2(0xff) == 0x7f);
}

TEST_CASE("sample conversion from 16-bit to 8-bit")
{
auto cv1 = lol::audio::sample::convert<int16_t, int8_t>;
CHECK(cv1(-0x8000) == -0x80);
CHECK(cv1(-0x4000) == -0x40);
CHECK(cv1(-0x0080) == -0x01);
CHECK(cv1(-0x0001) == -0x01);
CHECK(cv1( 0x0000) == 0x00);
CHECK(cv1( 0x00ff) == 0x00);
CHECK(cv1( 0x3fff) == 0x3f);
CHECK(cv1( 0x7fff) == 0x7f);

auto cv2 = lol::audio::sample::convert<uint16_t, int8_t>;
CHECK(cv2(0x0000) == -0x80);
CHECK(cv2(0x4000) == -0x40);
CHECK(cv2(0x7f80) == -0x01);
CHECK(cv2(0x7fff) == -0x01);
CHECK(cv2(0x8000) == 0x00);
CHECK(cv2(0x80ff) == 0x00);
CHECK(cv2(0xbfff) == 0x3f);
CHECK(cv2(0xffff) == 0x7f);
}

TEST_CASE("round-trip conversion from 8-bit to 8-bit")
{
auto cv1 = lol::audio::sample::convert<int8_t, float>;
auto cv2 = lol::audio::sample::convert<float, int8_t>;
CHECK(cv2(cv1(-128)) == -128);
CHECK(cv2(cv1(-127)) == -127);
CHECK(cv2(cv1( -64)) == -64);
CHECK(cv2(cv1( -32)) == -32);
CHECK(cv2(cv1( -2)) == -2);
CHECK(cv2(cv1( -1)) == -1);
CHECK(cv2(cv1( 0)) == 0);
CHECK(cv2(cv1( 1)) == 1);
CHECK(cv2(cv1( 2)) == 2);
CHECK(cv2(cv1( 32)) == 32);
CHECK(cv2(cv1( 64)) == 64);
CHECK(cv2(cv1( 127)) == 127);
CHECK(cv2(cv1(-0x80)) == -0x80);
CHECK(cv2(cv1(-0x7f)) == -0x7f);
CHECK(cv2(cv1(-0x40)) == -0x40);
CHECK(cv2(cv1(-0x20)) == -0x20);
CHECK(cv2(cv1(-0x02)) == -0x02);
CHECK(cv2(cv1(-0x01)) == -0x01);
CHECK(cv2(cv1( 0x00)) == 0x00);
CHECK(cv2(cv1( 0x01)) == 0x01);
CHECK(cv2(cv1( 0x02)) == 0x02);
CHECK(cv2(cv1( 0x20)) == 0x20);
CHECK(cv2(cv1( 0x40)) == 0x40);
CHECK(cv2(cv1( 0x7f)) == 0x7f);
}

Notiek ielāde…
Atcelt
Saglabāt