diff --git a/libvisual/libvisual/private/lv_audio_convert.cpp b/libvisual/libvisual/private/lv_audio_convert.cpp index afc38be2..f0f9a393 100644 --- a/libvisual/libvisual/private/lv_audio_convert.cpp +++ b/libvisual/libvisual/private/lv_audio_convert.cpp @@ -23,208 +23,186 @@ #include "lv_audio_convert.hpp" #include "lv_audio.h" #include "lv_mem.h" +#include #include #include -#include #include +#include +#include using namespace LV; namespace { - template - constexpr bool is_same_signedness_v = - (std::is_signed_v && std::is_signed_v) || - (std::is_unsigned_v && std::is_unsigned_v); + // Checks whether two integral types are both signed or unsigned. + template + constexpr bool is_same_signedness_v = std::is_signed_v == std::is_signed_v; - template - constexpr T half_range () - { - return std::numeric_limits::max (); - } + // Checks whether an integral type is promoted to unsigned or signed int in arithmetic operations. + template + constexpr bool int_promotable_v = sizeof (T) <= sizeof (int); - template - constexpr T half_range () - { - return std::numeric_limits::max () / 2 + 1; - } + // Checks if an integral type can be exactly representable with a float i.e. fits within the + // mantissa without loss. + template + constexpr bool is_float_representable_v = std::numeric_limits::digits <= std::numeric_limits::digits; - template - constexpr T zero () - { - return 0; - } - - template - constexpr T zero () - { - return std::numeric_limits::max () / 2 + 1; - } + // Type constraint for integral samples. + template + concept integral_sample = std::integral && int_promotable_v; - template - constexpr int shifter() - { - if constexpr (sizeof (S) > sizeof (D)) - return int (sizeof (S) - sizeof (D)) << 3; - else - return int (sizeof (D) - sizeof (S)) << 3; - } + // Type constraint for signed integral samples. + template + concept signed_integral_sample = std::signed_integral && int_promotable_v; - // same format conversion + // Type constraint for unsigned integral samples. template - inline void convert_sample_array (T* dst, T const* src, std::size_t count) + concept unsigned_integral_sample = std::unsigned_integral && int_promotable_v; + + // Returns the 'bias' of an unsigned integral type i.e. the offset that can be added to the minimum value of its + // signed counterpart to get 0. + // Example: The bias for uint8_t is 128 as the minimum value of an int8_t is -128. + template + constexpr auto bias () -> unsigned int { - visual_mem_copy (dst, src, sizeof (T) * count); + return 1U << (std::numeric_limits::digits - 1); } - // signed->unsigned int conversion (same width) - template - requires (sizeof (D) == sizeof (S)) - inline void convert_sample_array (D* dst, S const* src, std::size_t count) + // Converts an integral sample to an unsigned integral sample of the same width. + // Reduces to an identity function if handed an unsigned integral sample. + // Example: int16_t and uint16_t samples are both converted to uint16_t. + template + constexpr auto to_unsigned_sample (T x) -> std::make_unsigned_t { - constexpr auto a {zero ()}; - - auto src_end {src + count}; - - while (src != src_end) { - *dst = *src + a; - dst++; - src++; + if constexpr (std::is_signed_v) { + using U = std::make_unsigned_t; + return x + bias (); + } else { + return x; } } - // unsigned->signed int conversion (same width) - template - requires (sizeof (D) == sizeof (S)) - inline void convert_sample_array (D* dst, S const* src, std::size_t count) + // Converts an integral sample to an signed integral sample of the same width. + // Reduces to an identity function if input is already unsigned. + // Example: int16_t and uint16_t samples are both converted to uint16_t. + template + constexpr auto to_signed_sample (T x) -> std::make_signed_t { - constexpr auto a {zero ()}; - - auto src_end {src + count}; - - while (src != src_end) { - *dst = *src - a; - dst++; - src++; + if constexpr (!std::is_signed_v) { + return x - bias (); + } else { + return x; } } - // int->float conversions - template - inline void convert_sample_array (float* dst, S const* src, std::size_t count) + // Converts an integral sample to a narrower or wider integral type with the + // same signedness. + // + // Caveat: Due to the use of shifts for performance instead of float multiplications, the relative error in _widening_ + // conversions can get as large as 0.038 to 0.039% of the true value (e.g. converting from 8-bit to 16/32-bit). + template + constexpr auto widen_or_narrow_sample (T x) -> U + requires is_same_signedness_v { - constexpr float a {1.0 / float (half_range ())}; - constexpr float b {-zero() * a}; - - S const* src_end = src + count; + constexpr auto n_t {std::numeric_limits::digits}; + constexpr auto n_u {std::numeric_limits::digits}; - while (src != src_end) { - *dst = *src * a + b; - dst++; - src++; + if constexpr (n_t > n_u) { + return x >> (n_t - n_u); + } else if constexpr (n_t < n_u) { + return x << (n_u - n_t); + } else { + return x; } } - // float->int conversions - template - inline void convert_sample_array (D* dst, float const* src, std::size_t count) + // Converts an integral sample to 32-bit float. + template + constexpr auto to_float_sample (T x) -> float { - constexpr auto a {float (half_range ())}; - constexpr auto b {zero ()}; + using U = std::make_unsigned_t; + using F = std::conditional_t, float, double>; - auto src_end {src + count}; + constexpr F factor = 1.0 / (0.5 * std::numeric_limits::max ()); + constexpr F one = 1.0; - while (src != src_end) { - *dst = *src * a + b; - dst++; - src++; - } + return to_unsigned_sample (x) * factor - one; } - // narrowing/widening int conversion (same signedness) - template - requires (is_same_signedness_v && sizeof (D) != sizeof (S)) - inline void convert_sample_array (D* dst, S const* src, std::size_t count) + // Converts a 32-bit float sample to an integral type. + template + constexpr auto from_float_sample (float x) -> T { - constexpr auto shift {shifter ()}; + using U = std::make_unsigned_t; + using F = std::conditional_t, float, double>; - auto src_end {src + count}; + constexpr F factor = 0.5 * std::numeric_limits::max (); + constexpr F one = 1.0; - if constexpr (sizeof (S) > sizeof (D)) { - // narrowing - while (src != src_end) { - *dst = *src >> shift; - dst++; - src++; - } + auto y {static_cast ((x + one) * factor)}; + + if constexpr (std::is_signed_v) { + return to_signed_sample (y); } else { - // widening - while (src != src_end) { - *dst = *src << shift; - dst++; - src++; - } + return y; } } - // narrowing/widening unsigned->signed int conversion - template - requires (sizeof (D) != sizeof (S)) - inline void convert_sample_array (D* dst, S const* src, std::size_t count) + // Overload for same format conversions. + template + constexpr void convert_samples (std::span dst, std::span src) { - constexpr auto a {zero()}; - constexpr auto shift {shifter ()}; - - auto src_end {src + count}; + auto count {std::min (src.size (), dst.size ())}; + visual_mem_copy (dst.data (), src.data (), count * sizeof (T)); + } - if constexpr (sizeof (D) < sizeof (S)) { - // narrowing - while (src != src_end) { - *dst = D(*src >> shift) - a; - dst++; - src++; - } - } else { - // widening - while (src != src_end) { - *dst = D(*src << shift) - a; - dst++; - src++; - } - } + // Overload for unsigned to unsigned integral conversions. + template + constexpr void convert_samples (std::span dst, std::span src) + { + auto const count {std::min (src.size (), dst.size ())}; + std::transform (src.begin (), src.begin () + count, dst.begin (), + [=] (auto x) { + auto y {to_unsigned_sample (x)}; + return widen_or_narrow_sample (y); + }); } - // narrowing/widening signed->unsigned int conversion - template - requires (sizeof (D) != sizeof (S)) - inline void convert_sample_array (D* dst, S const* src, std::size_t count) + // Overload for signed/unsigned to signed integral conversions. + template + constexpr void convert_samples (std::span dst, std::span src) { - constexpr auto a {zero()}; - constexpr auto shift {shifter ()}; + auto const count {std::min (src.size (), dst.size ())}; + std::transform (src.begin (), src.begin () + count, dst.begin (), + [=] (auto x) { + auto y {to_signed_sample (x)}; + return widen_or_narrow_sample (y); + }); + } - auto src_end {src + count}; + // Overload for integral to float conversions. + template + constexpr void convert_samples (std::span dst, std::span src) + { + auto const count {std::min (src.size (), dst.size ())}; + std::transform (src.begin (), src.begin () + count, dst.begin (), to_float_sample); + } - if constexpr (sizeof (D) < sizeof (S)) { - // narrowing - while (src != src_end) { - *dst = D(*src >> shift) + a; - dst++; - src++; - } - } else { - // widening - while (src != src_end) { - *dst = D(*src << shift) + a; - dst++; - src++; - } - } + // Overload for float to integral conversions. + template + constexpr void convert_samples (std::span dst, std::span src) + { + auto const count {std::min (src.size (), dst.size ())}; + std::transform (src.begin (), src.begin () + count, dst.begin (), from_float_sample); } + // Wrapper for convert_samples() for storing all of its variants in a single table. template - void convert (void* dst, void const* src, std::size_t size) + void convert (void* dst, void const* src, std::size_t count) { - convert_sample_array (static_cast (dst), static_cast (src), size / sizeof (S)); + std::span const src_span {static_cast (src), count}; + std::span const dst_span {static_cast (dst), count}; + convert_samples (dst_span, src_span); } using ConvertFunc = void (*) (void*, void const*, std::size_t);