/* * SPDX-License-Identifier: GPL-2.0-or-later * * Copyright (C) 2020-2022 The DOSBox Staging Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DOSBOX_MATH_UTILS_H #define DOSBOX_MATH_UTILS_H #include #include #include "support.h" // Clamp: given a value that can be compared with the given minimum and maximum // values, this function will: // * return the value if it's in-between or equal to either bounds, or // * return either bound depending on which bound the value is beyond template T clamp(const T& n, const T& lower, const T& upper) { return std::max(lower, std::min(n, upper)); } /* Returns a number wrapped between the lower and upper bounds. - wrap(-1, 0, 4); // Returns 4 - wrap(5, 0, 4); // Returns 0 All credit to Charles Bailey, https://stackoverflow.com/a/707426 */ constexpr int wrap(int val, int const lower_bound, int const upper_bound) { const auto range_size = upper_bound - lower_bound + 1; if (val < lower_bound) val += range_size * ((lower_bound - val) / range_size + 1); return lower_bound + (val - lower_bound) % range_size; } // Unsigned-only integer division with ceiling template inline constexpr T1 ceil_udivide(const T1 x, const T2 y) noexcept { static_assert(std::is_unsigned::value, "First parameter should be unsigned"); static_assert(std::is_unsigned::value, "Second parameter should be unsigned"); return (x != 0) ? 1 + ((x - 1) / y) : 0; // https://stackoverflow.com/a/2745086 } // Signed-only integer division with ceiling template inline constexpr T1 ceil_sdivide(const T1 x, const T2 y) noexcept { static_assert(std::is_signed::value, "First parameter should be signed"); static_assert(std::is_signed::value, "Second parameter should be signed."); return x / y + (((x < 0) ^ (y > 0)) && (x % y)); // https://stackoverflow.com/a/33790603 } template std::function CreateRandomizer(const T min_value, const T max_value); inline int iround(double x) { assert(std::isfinite(x)); assert(x >= (std::numeric_limits::min)()); assert(x <= (std::numeric_limits::max)()); return static_cast(round(x)); } inline int iroundf(const float x) { assert(std::isfinite(x)); assert(x >= static_cast((std::numeric_limits::min)())); assert(x <= static_cast((std::numeric_limits::max)())); return static_cast(roundf(x)); } // Left-shifts a signed value by a given amount, with overflow detection template constexpr T1 left_shift_signed(T1 value, T2 amount) { // Ensure we're using a signed type static_assert(std::is_signed::value, "T1 must be signed"); // Ensure the two types are integers static_assert(std::numeric_limits::is_integer, "T1 must be an integer type"); static_assert(std::numeric_limits::is_integer, "T2 must be an integer type"); // Ensure the amount we're shifting isn't negative assert(amount >= 0); // Ensure the amount we're shifting doesn't exceed the value's bit-size assert(amount <= std::numeric_limits::digits); #if defined(NDEBUG) // For release builds, simply cast the value to the unsigned-equivalent // to ensure performance isn't impacted. Debug and UBSAN builds catch issues. typedef typename std::make_unsigned::type unsigned_T1; const auto shifted = static_cast(value) << amount; #else // Ensure we can accommodate the value being shifted static_assert(sizeof(T1) <= sizeof(next_uint_t), "T1 cannot be a larger than its next larger type"); // cast the value to the next larger unsigned-type before shifting const auto shifted = static_cast>(value) << amount; // Ensure the value is in-bounds of its signed limits assert(static_cast>(shifted) >= (std::numeric_limits::min)()); assert(static_cast>(shifted) <= (std::numeric_limits::max)()); #endif // Cast it back to its original type return static_cast(shifted); } template int8_t clamp_to_int8(const T val) { constexpr auto min_val = static_cast(std::is_signed{} ? INT8_MIN : 0); constexpr auto max_val = static_cast(INT8_MAX); return static_cast(std::clamp(val, min_val, max_val)); } template int16_t clamp_to_int16(const T val) { constexpr auto min_val = static_cast(std::is_signed{} ? INT16_MIN : 0); constexpr auto max_val = static_cast(INT16_MAX); return static_cast(std::clamp(val, min_val, max_val)); } template int32_t clamp_to_int32(const T val) { constexpr auto min_val = static_cast(std::is_signed{} ? INT32_MIN : 0); constexpr auto max_val = static_cast(INT32_MAX); return static_cast(std::clamp(val, min_val, max_val)); } inline float decibel_to_gain(const float decibel) { return powf(10.0f, decibel / 20.0f); } inline float gain_to_decibel(const float gain) { return 20.0f * logf(gain) / logf(10.0f); } // A wrapper to convert a scalar gain to a percentage. // This avoids having a bunch of magic *100.0 throughout the code. constexpr float gain_to_percentage(const float gain) { return gain * 100.0f; } // A wrapper to convert a percentage into a scalar gain. // This avoids having a bunch of magic /100.0 throughout the code. constexpr float percentage_to_gain(const float percentage) { return percentage / 100.0f; } template constexpr T lerp(const T a, const T b, const T t) { return a * (1 - t) + b * t; } template constexpr T invlerp(const T a, const T b, const T v) { return (v - a) / (b - a); } template constexpr T remap(const T in_min, const T in_max, const T out_min, const T out_max, const T v) { const auto t = invlerp(in_min, in_max, v); return lerp(out_min, out_max, t); } // Explicit instantiations for lerp, invlerp, and remap template float lerp(const float a, const float b, const float t); template double lerp(const double a, const double b, const double t); template float invlerp(const float a, const float b, const float v); template double invlerp(const double a, const double b, const double v); template float remap(const float in_min, const float in_max, const float out_min, const float out_max, const float v); template double remap(const double in_min, const double in_max, const double out_min, const double out_max, const double v); #endif