lmms
Cross-platform music production software
Loading...
Searching...
No Matches
lmms_math.h
Go to the documentation of this file.
1/*
2 * lmms_math.h - defines math functions
3 *
4 * Copyright (c) 2004-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5 *
6 * This file is part of LMMS - https://lmms.io
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public
19 * License along with this program (see COPYING); if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301 USA.
22 *
23 */
24
25#ifndef LMMS_MATH_H
26#define LMMS_MATH_H
27
28#include <QtGlobal>
29#include <algorithm>
30#include <cassert>
31#include <cmath>
32#include <cstdint>
33#include <cstring>
34#include <numbers>
35#include <concepts>
36
37#include "lmms_constants.h"
38
39#ifdef __SSE2__
40#include <emmintrin.h>
41#endif
42
43namespace lmms
44{
45
46
47// TODO C++23: Make constexpr since std::abs() will be constexpr
48inline bool approximatelyEqual(float x, float y) noexcept
49{
50 return x == y || std::abs(x - y) < F_EPSILON;
51}
52
53// TODO C++23: Make constexpr since std::trunc() will be constexpr
63inline auto fraction(std::floating_point auto x) noexcept
64{
65 return x - std::trunc(x);
66}
67
68
69// TODO C++23: Make constexpr since std::floor() will be constexpr
80inline auto absFraction(std::floating_point auto x) noexcept
81{
82 return x - std::floor(x);
83}
84
85
88inline int fastRand() noexcept
89{
90 thread_local unsigned long s_next = 1;
91 s_next = s_next * 1103515245 + 12345;
92 return s_next / 65536 % 32768;
93}
94
95
98template<std::floating_point T>
99inline T fastRand(T upper) noexcept
100{
101 constexpr auto FAST_RAND_RATIO = static_cast<T>(1.0 / 32768);
102 return fastRand() * upper * FAST_RAND_RATIO;
103}
104
105
109template<std::integral T>
110inline T fastRand(T upper) noexcept
111{
112 constexpr float FAST_RAND_RATIO = 1.f / 32768;
113 return static_cast<T>(fastRand() * static_cast<float>(upper) * FAST_RAND_RATIO);
114}
115
116
119template<typename T> requires std::is_arithmetic_v<T>
120inline auto fastRand(T from, T to) noexcept { return from + fastRand(to - from); }
121
122
125template<std::floating_point T>
126inline T fastRandInc(T upper) noexcept
127{
128 constexpr auto FAST_RAND_RATIO = static_cast<T>(1.0 / 32767);
129 return fastRand() * upper * FAST_RAND_RATIO;
130}
131
132
135template<std::unsigned_integral T>
136inline T fastRandInc(T upper) noexcept
137{
138 // The integer specialization of this function is kind of weird, but it is
139 // necessary to prevent massive bias away from the maximum value.
140 // FAST_RAND_RATIO here is 1 greater than normal, so when multiplied by, it
141 // will result in a random float within [0, @p upper) instead of the usual
142 // [0, @p upper].
143 constexpr float FAST_RAND_RATIO = 1.f / 32768;
144 // Since the random float will be in a range that does not include @upper
145 // due to the above ratio, increase the upper bound by 1.
146 // All values greater than @p upper get rounded down to @p upper, making the
147 // chance of returning a value of @p upper the same as any other of the
148 // possible values.
149 // No need to copysign() unlike the signed_integral overload, since it will always be positive
150 return static_cast<T>(fastRand() * (upper + 1.f) * FAST_RAND_RATIO);
151}
152
153
157template<std::signed_integral T>
158inline T fastRandInc(T upper) noexcept
159{
160 // The integer specialization of this function is kind of weird, but it is
161 // necessary to prevent massive bias away from the maximum value.
162 // FAST_RAND_RATIO here is 1 greater than normal, so when multiplied by, it
163 // will result in a random float within [0, @p upper) instead of the usual
164 // [0, @p upper].
165 constexpr float FAST_RAND_RATIO = 1.f / 32768;
166 // Since the random float will be in a range that does not include @upper
167 // due to the above ratio, increase the magnitude of the upper bound by 1.
168 // All values greater than @p upper get rounded down to @p upper, making the
169 // chance of returning a value of @p upper the same as any other of the
170 // possible values.
171 // HACK: Even on -O3, without this static_cast, it will convert @p upper to float twice for some reason
172 const auto fupper = static_cast<float>(upper);
173 const float r = fupper + std::copysign(1.f, fupper);
174 // Always round towards 0 (implicit truncation occurs during static_cast).
175 return static_cast<T>(fastRand() * r * FAST_RAND_RATIO);
176}
177
178
183template<typename T> requires std::is_arithmetic_v<T>
184inline auto fastRandInc(T from, T to) noexcept { return from + fastRandInc(to - from); }
185
186
188inline bool oneIn(unsigned chance) noexcept { return 0 == (fastRand() % chance); }
189
190
192template<class T>
193static void roundAt(T& value, const T& where, const T& stepSize)
194{
195 if (std::abs(value - where) < F_EPSILON * std::abs(stepSize))
196 {
197 value = where;
198 }
199}
200
202inline double fastPow(double a, double b)
203{
204 double d;
205 std::int32_t x[2];
206
207 std::memcpy(x, &a, sizeof(x));
208 x[1] = static_cast<std::int32_t>(b * (x[1] - 1072632447) + 1072632447);
209 x[0] = 0;
210
211 std::memcpy(&d, x, sizeof(d));
212 return d;
213}
214
215
217template<typename T>
218constexpr T sign(T val) noexcept
219{
220 return val >= 0 ? 1 : -1;
221}
222
223
225inline float sqrt_neg(float val)
226{
227 return std::sqrt(std::abs(val)) * sign(val);
228}
229
231inline float signedPowf(float v, float e)
232{
233 return std::pow(std::abs(v), e) * sign(v);
234}
235
236
241inline float logToLinearScale(float min, float max, float value)
242{
243 using namespace std::numbers;
244 if (min < 0)
245 {
246 const float mmax = std::max(std::abs(min), std::abs(max));
247 const float val = value * (max - min) + min;
248 float result = signedPowf(val / mmax, e_v<float>) * mmax;
249 return std::isnan(result) ? 0 : result;
250 }
251 float result = std::pow(value, e_v<float>) * (max - min) + min;
252 return std::isnan(result) ? 0 : result;
253}
254
255
257inline float linearToLogScale(float min, float max, float value)
258{
259 constexpr auto inv_e = static_cast<float>(1.0 / std::numbers::e);
260 const float valueLimited = std::clamp(value, min, max);
261 const float val = (valueLimited - min) / (max - min);
262 if (min < 0)
263 {
264 const float mmax = std::max(std::abs(min), std::abs(max));
265 float result = signedPowf(valueLimited / mmax, inv_e) * mmax;
266 return std::isnan(result) ? 0 : result;
267 }
268 float result = std::pow(val, inv_e) * (max - min) + min;
269 return std::isnan(result) ? 0 : result;
270}
271
272
273// TODO C++26: Make constexpr since std::exp() will be constexpr
274template<typename T> requires std::is_arithmetic_v<T>
275inline auto fastPow10f(T x)
276{
277 using F_T = std::conditional_t<std::is_floating_point_v<T>, T, float>;
278 return std::exp(std::numbers::ln10_v<F_T> * x);
279}
280
281
282// TODO C++26: Make constexpr since std::log() will be constexpr
283template<typename T> requires std::is_arithmetic_v<T>
284inline auto fastLog10f(T x)
285{
286 using F_T = std::conditional_t<std::is_floating_point_v<T>, T, float>;
287 constexpr auto inv_ln10 = static_cast<F_T>(1.0 / std::numbers::ln10);
288 return std::log(x) * inv_ln10;
289}
290
291
295inline float ampToDbfs(float amp)
296{
297 return fastLog10f(amp) * 20.0f;
298}
299
300
304inline float dbfsToAmp(float dbfs)
305{
306 return fastPow10f(dbfs * 0.05f);
307}
308
309
313inline float safeAmpToDbfs(float amp)
314{
315 return amp == 0.0f ? -INFINITY : ampToDbfs(amp);
316}
317
318
322inline float safeDbfsToAmp(float dbfs)
323{
324 return std::isinf(dbfs) ? 0.0f : dbfsToAmp(dbfs);
325}
326
327
328// TODO C++20: use std::formatted_size
329// @brief Calculate number of digits which LcdSpinBox would show for a given number
330inline int numDigitsAsInt(float f)
331{
332 // use rounding:
333 // LcdSpinBox sometimes uses std::round(), sometimes cast rounding
334 // we use rounding to be on the "safe side"
335 int asInt = static_cast<int>(std::round(f));
336 int digits = 1; // always at least 1
337 if(asInt < 0)
338 {
339 ++digits;
340 asInt = -asInt;
341 }
342 // "asInt" is positive from now
343 int power = 1;
344 for (int i = 1; i < 10; ++i)
345 {
346 power *= 10;
347 if (asInt >= power) { ++digits; } // 2 digits for >=10, 3 for >=100
348 else { break; }
349 }
350 return digits;
351}
352
353template <typename T>
355{
356public:
357 LinearMap(T x1, T y1, T x2, T y2)
358 {
359 T const dx = x2 - x1;
360 assert (dx != T(0));
361
362 m_a = (y2 - y1) / dx;
363 m_b = y1 - m_a * x1;
364 }
365
366 T map(T x) const
367 {
368 return m_a * x + m_b;
369 }
370
371private:
374};
375
376#ifdef __SSE2__
377// exp approximation for SSE2: https://stackoverflow.com/a/47025627/5759631
378// Maximum relative error of 1.72863156e-3 on [-87.33654, 88.72283]
379inline __m128 fastExp(__m128 x)
380{
381 __m128 f, p, r;
382 __m128i t, j;
383 const __m128 a = _mm_set1_ps (12102203.0f); /* (1 << 23) / log(2) */
384 const __m128i m = _mm_set1_epi32 (0xff800000); /* mask for integer bits */
385 const __m128 ttm23 = _mm_set1_ps (1.1920929e-7f); /* exp2(-23) */
386 const __m128 c0 = _mm_set1_ps (0.3371894346f);
387 const __m128 c1 = _mm_set1_ps (0.657636276f);
388 const __m128 c2 = _mm_set1_ps (1.00172476f);
389
390 t = _mm_cvtps_epi32 (_mm_mul_ps (a, x));
391 j = _mm_and_si128 (t, m); /* j = (int)(floor (x/log(2))) << 23 */
392 t = _mm_sub_epi32 (t, j);
393 f = _mm_mul_ps (ttm23, _mm_cvtepi32_ps (t)); /* f = (x/log(2)) - floor (x/log(2)) */
394 p = c0; /* c0 */
395 p = _mm_mul_ps (p, f); /* c0 * f */
396 p = _mm_add_ps (p, c1); /* c0 * f + c1 */
397 p = _mm_mul_ps (p, f); /* (c0 * f + c1) * f */
398 p = _mm_add_ps (p, c2); /* p = (c0 * f + c1) * f + c2 ~= 2^f */
399 r = _mm_castsi128_ps (_mm_add_epi32 (j, _mm_castps_si128 (p))); /* r = p * 2^i*/
400 return r;
401}
402
403// Lost Robot's SSE2 adaptation of Kari's vectorized log approximation: https://stackoverflow.com/a/65537754/5759631
404// Maximum relative error of 7.922410e-4 on [1.0279774e-38f, 3.4028235e+38f]
405inline __m128 fastLog(__m128 a)
406{
407 __m128i aInt = _mm_castps_si128(a);
408 __m128i e = _mm_sub_epi32(aInt, _mm_set1_epi32(0x3f2aaaab));
409 e = _mm_and_si128(e, _mm_set1_epi32(0xff800000));
410 __m128i subtr = _mm_sub_epi32(aInt, e);
411 __m128 m = _mm_castsi128_ps(subtr);
412 __m128 i = _mm_mul_ps(_mm_cvtepi32_ps(e), _mm_set1_ps(1.19209290e-7f));
413 __m128 f = _mm_sub_ps(m, _mm_set1_ps(1.0f));
414 __m128 s = _mm_mul_ps(f, f);
415 __m128 r = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(0.230836749f), f), _mm_set1_ps(-0.279208571f));
416 __m128 t = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(0.331826031f), f), _mm_set1_ps(-0.498910338f));
417 r = _mm_add_ps(_mm_mul_ps(r, s), t);
418 r = _mm_add_ps(_mm_mul_ps(r, s), f);
419 r = _mm_add_ps(_mm_mul_ps(i, _mm_set1_ps(0.693147182f)), r);
420 return r;
421}
422
423inline __m128 sse2Abs(__m128 x)
424{
425 return _mm_and_ps(x, _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)));// clear sign bit
426}
427
428inline __m128 sse2Floor(__m128 x)
429{
430 __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(x)); // trunc toward 0
431 __m128 needs_correction = _mm_cmplt_ps(x, t); // checks if x < trunc
432 return _mm_sub_ps(t, _mm_and_ps(needs_correction, _mm_set1_ps(1.0f)));
433}
434
435inline __m128 sse2Round(__m128 x)
436{
437 __m128 sign_mask = _mm_cmplt_ps(x, _mm_setzero_ps());// checks if x < 0
438 __m128 bias_pos = _mm_set1_ps(0.5f);
439 __m128 bias_neg = _mm_set1_ps(-0.5f);
440 __m128 bias = _mm_or_ps(_mm_and_ps(sign_mask, bias_neg), _mm_andnot_ps(sign_mask, bias_pos));
441 __m128 y = _mm_add_ps(x, bias);
442 return _mm_cvtepi32_ps(_mm_cvttps_epi32(y));
443}
444
445#endif // __SSE2__
446
447} // namespace lmms
448
449#endif // LMMS_MATH_H
LinearMap(T x1, T y1, T x2, T y2)
Definition lmms_math.h:357
T map(T x) const
Definition lmms_math.h:366
T m_a
Definition lmms_math.h:372
T m_b
Definition lmms_math.h:373
Definition RemotePluginBase.cpp:34
float linearToLogScale(float min, float max, float value)
Scales value from logarithmic to linear. Value should be in min-max range.
Definition lmms_math.h:257
float safeAmpToDbfs(float amp)
Converts linear amplitude (0-1.0) to dBFS scale. Handles zeroes as -inf.
Definition lmms_math.h:313
auto fastPow10f(T x)
Definition lmms_math.h:275
float dbfsToAmp(float dbfs)
Converts dBFS-scale to linear amplitude with 0dBFS = 1.0.
Definition lmms_math.h:304
T fastRandInc(T upper) noexcept
Returns a pseudorandom number within [0, upper] (inclusive upper bound).
Definition lmms_math.h:126
int numDigitsAsInt(float f)
Definition lmms_math.h:330
float signedPowf(float v, float e)
Exponential function that deals with negative bases.
Definition lmms_math.h:231
float sqrt_neg(float val)
if val >= 0.0f, returns sqrt(val), else: -sqrt(-val)
Definition lmms_math.h:225
float logToLinearScale(float min, float max, float value)
Scales value from linear to logarithmic.
Definition lmms_math.h:241
double fastPow(double a, double b)
Source: http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/.
Definition lmms_math.h:202
static void roundAt(T &value, const T &where, const T &stepSize)
Round value to where depending on step size.
Definition lmms_math.h:193
auto absFraction(std::floating_point auto x) noexcept
Returns the wrapped fractional part of a float, a value between 0.0f and 1.0f.
Definition lmms_math.h:80
auto fastLog10f(T x)
Definition lmms_math.h:284
float ampToDbfs(float amp)
Converts linear amplitude (>0-1.0) to dBFS scale.
Definition lmms_math.h:295
float safeDbfsToAmp(float dbfs)
Converts dBFS-scale to linear amplitude with 0dBFS = 1.0. Handles infinity as zero.
Definition lmms_math.h:322
int fastRand() noexcept
Returns a pseudorandom integer within [0, 32768).
Definition lmms_math.h:88
constexpr float F_EPSILON
Definition lmms_constants.h:35
constexpr T sign(T val) noexcept
returns +1 if val >= 0, else -1
Definition lmms_math.h:218
bool approximatelyEqual(float x, float y) noexcept
Definition lmms_math.h:48
auto fraction(std::floating_point auto x) noexcept
Returns the fractional part of a float, a value between -1.0f and 1.0f.
Definition lmms_math.h:63
bool oneIn(unsigned chance) noexcept
Returns true one in chance times at random.
Definition lmms_math.h:188