Improve lmms::fastRand() and use it instead of std::rand() (#7741)

This commit is contained in:
Fawn
2026-01-15 17:12:04 -07:00
committed by GitHub
parent b390fa414a
commit 5e3a67c9d1
23 changed files with 433 additions and 468 deletions

View File

@@ -162,7 +162,7 @@ public:
static inline sample_t noiseSample( const float )
{
return 1.0f - rand() * 2.0f / static_cast<float>(RAND_MAX);
return fastRandInc(-1.f, 1.f);
}
static sample_t userWaveSample(const SampleBuffer* buffer, const float sample)

View File

@@ -43,6 +43,7 @@
namespace lmms
{
// TODO C++23: Make constexpr since std::abs() will be constexpr
inline bool approximatelyEqual(float x, float y) noexcept
{
@@ -81,26 +82,112 @@ inline auto absFraction(std::floating_point auto x) noexcept
return x - std::floor(x);
}
inline auto fastRand() noexcept
//! @brief Returns a pseudorandom integer within [0, 32768).
//! @returns A pseudorandom integer greater than or equal to 0 and less than 32767.
inline int fastRand() noexcept
{
static unsigned long next = 1;
next = next * 1103515245 + 12345;
return next / 65536 % 32768;
thread_local unsigned long s_next = 1;
s_next = s_next * 1103515245 + 12345;
return s_next / 65536 % 32768;
}
//! @brief Returns a pseudorandom number within [0, @p upper) (exclusive upper bound).
//! @returns A pseudorandom number greater than or equal to 0 and less @p upper.
template<std::floating_point T>
inline auto fastRand(T range) noexcept
inline T fastRand(T upper) noexcept
{
constexpr T FAST_RAND_RATIO = static_cast<T>(1.0 / 32767);
return fastRand() * range * FAST_RAND_RATIO;
constexpr auto FAST_RAND_RATIO = static_cast<T>(1.0 / 32768);
return fastRand() * upper * FAST_RAND_RATIO;
}
template<std::floating_point T>
inline auto fastRand(T from, T to) noexcept
//! @brief Returns a pseudorandom integer within [0, @p upper) (exclusive upper bound).
//! @p upper may be negative, in which case the output range is (@p upper, 0].
//! @returns A pseudorandom integer greater than or equal to 0 and less than @p upper.
template<std::integral T>
inline T fastRand(T upper) noexcept
{
return from + fastRand(to - from);
constexpr float FAST_RAND_RATIO = 1.f / 32768;
return static_cast<T>(fastRand() * static_cast<float>(upper) * FAST_RAND_RATIO);
}
//! @brief Returns a pseudorandom integer within [@p from, @p to) (exclusive upper bound).
//! @returns A pseudorandom integer greater than or equal to @p from and less than @p to.
template<typename T> requires std::is_arithmetic_v<T>
inline auto fastRand(T from, T to) noexcept { return from + fastRand(to - from); }
//! @brief Returns a pseudorandom number within [0, @p upper] (inclusive upper bound).
//! @returns A pseudorandom number greater than or equal to 0 and less than or equal to @p upper.
template<std::floating_point T>
inline T fastRandInc(T upper) noexcept
{
constexpr auto FAST_RAND_RATIO = static_cast<T>(1.0 / 32767);
return fastRand() * upper * FAST_RAND_RATIO;
}
//! @brief Returns a pseudorandom integer within [0, @p upper] (inclusive upper bound).
//! @returns A pseudorandom integer greater than or equal to 0 and less than or equal to @p upper.
template<std::unsigned_integral T>
inline T fastRandInc(T upper) noexcept
{
// The integer specialization of this function is kind of weird, but it is
// necessary to prevent massive bias away from the maximum value.
// FAST_RAND_RATIO here is 1 greater than normal, so when multiplied by, it
// will result in a random float within [0, @p upper) instead of the usual
// [0, @p upper].
constexpr float FAST_RAND_RATIO = 1.f / 32768;
// Since the random float will be in a range that does not include @upper
// due to the above ratio, increase the upper bound by 1.
// All values greater than @p upper get rounded down to @p upper, making the
// chance of returning a value of @p upper the same as any other of the
// possible values.
// No need to copysign() unlike the signed_integral overload, since it will always be positive
return static_cast<T>(fastRand() * (upper + 1.f) * FAST_RAND_RATIO);
}
//! @brief Returns a pseudorandom integer within [0, @p upper] (inclusive upper bound).
//! @p upper may be negative, in which case the output range is [@p upper, 0].
//! @returns A pseudorandom integer greater than or equal to 0 and less than or equal to @p upper.
template<std::signed_integral T>
inline T fastRandInc(T upper) noexcept
{
// The integer specialization of this function is kind of weird, but it is
// necessary to prevent massive bias away from the maximum value.
// FAST_RAND_RATIO here is 1 greater than normal, so when multiplied by, it
// will result in a random float within [0, @p upper) instead of the usual
// [0, @p upper].
constexpr float FAST_RAND_RATIO = 1.f / 32768;
// Since the random float will be in a range that does not include @upper
// due to the above ratio, increase the magnitude of the upper bound by 1.
// All values greater than @p upper get rounded down to @p upper, making the
// chance of returning a value of @p upper the same as any other of the
// possible values.
// HACK: Even on -O3, without this static_cast, it will convert @p upper to float twice for some reason
const auto fupper = static_cast<float>(upper);
const float r = fupper + std::copysign(1.f, fupper);
// Always round towards 0 (implicit truncation occurs during static_cast).
return static_cast<T>(fastRand() * r * FAST_RAND_RATIO);
}
//! @brief Returns a pseudorandom integer within [@p from, @p to] (inclusive upper bound).
//! This function does not require the parameters to be in the proper order.
//! fastRand(a, b) behaves identically to fastRand(b, a).
//! @returns A pseudorandom integer greater than or equal to @p from and less than or equal to @p to.
template<typename T> requires std::is_arithmetic_v<T>
inline auto fastRandInc(T from, T to) noexcept { return from + fastRandInc(to - from); }
//! @brief Returns true one in @p chance times at random.
inline bool oneIn(unsigned chance) noexcept { return 0 == (fastRand() % chance); }
//! Round `value` to `where` depending on step size
template<class T>
static void roundAt(T& value, const T& where, const T& stepSize)
@@ -180,26 +267,26 @@ inline float linearToLogScale(float min, float max, float value)
return std::isnan(result) ? 0 : result;
}
// TODO C++26: Make constexpr since std::exp() will be constexpr
template<std::floating_point T>
template<typename T> requires std::is_arithmetic_v<T>
inline auto fastPow10f(T x)
{
return std::exp(std::numbers::ln10_v<T> * x);
using F_T = std::conditional_t<std::is_floating_point_v<T>, T, float>;
return std::exp(std::numbers::ln10_v<F_T> * x);
}
// TODO C++26: Make constexpr since std::exp() will be constexpr
inline auto fastPow10f(std::integral auto x)
{
return std::exp(std::numbers::ln10_v<float> * x);
}
// TODO C++26: Make constexpr since std::log() will be constexpr
inline auto fastLog10f(float x)
template<typename T> requires std::is_arithmetic_v<T>
inline auto fastLog10f(T x)
{
constexpr auto inv_ln10 = static_cast<float>(1.0 / std::numbers::ln10);
using F_T = std::conditional_t<std::is_floating_point_v<T>, T, float>;
constexpr auto inv_ln10 = static_cast<F_T>(1.0 / std::numbers::ln10);
return std::log(x) * inv_ln10;
}
//! @brief Converts linear amplitude (>0-1.0) to dBFS scale.
//! @param amp Linear amplitude, where 1.0 = 0dBFS. ** Must be larger than zero! **
//! @return Amplitude in dBFS.