DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
DspMath.h
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
16#include <algorithm>
17#include <array>
18#include <cmath>
19#include <concepts>
20#include <numbers>
21
22namespace dspark {
23
24// ============================================================================
25// Concepts
26// ============================================================================
27
29template <typename T>
30concept FloatType = std::floating_point<T>;
31
32namespace detail {
33// Shared coefficients of the existing reduced-range odd sine polynomial.
34// Nonlinear offline kernels also use its exact slope and residual bounds.
35template <FloatType T> inline constexpr std::array<T, 5> fastSinPolynomial{
36 T(0.99999997408724855), T(-0.16666646026660671), T(0.0083328727116396326),
37 T(-0.00019799239565814083), T(2.5871610835732768e-6)};
38} // namespace detail
39
40// ============================================================================
41// Constants
42// ============================================================================
43
45template <FloatType T> inline constexpr T pi = std::numbers::pi_v<T>;
46
48template <FloatType T> inline constexpr T twoPi = T(2) * std::numbers::pi_v<T>;
49
51template <FloatType T> inline constexpr T invTwoPi = T(1) / twoPi<T>;
52
54template <FloatType T> inline constexpr T halfPi = std::numbers::pi_v<T> / T(2);
55
57template <FloatType T> inline constexpr T sqrt2 = std::numbers::sqrt2_v<T>;
58
60template <FloatType T> inline constexpr T invSqrt2 = T(1) / std::numbers::sqrt2_v<T>;
61
62// ============================================================================
63// Decibel / Gain Conversions
64// ============================================================================
65
73template <FloatType T>
74[[nodiscard]] inline T decibelsToGain(T dB, T minusInfinityDb = T(-100)) noexcept
75{
76 // exp(dB * ln(10)/20): the same accuracy as pow(10, dB/20) (measured
77 // 2.97e-7 vs 2.78e-7 relative in float over +-60 dB) at half the cost.
78 return dB <= minusInfinityDb ? T(0) : std::exp(dB * T(0.11512925464970228420));
79}
80
88template <FloatType T>
89[[nodiscard]] inline T gainToDecibels(T gain, T minusInfinityDb = T(-100)) noexcept
90{
91 // 20/ln(10) * ln(gain): log() is about twice as fast as log10() in float
92 // at the same accuracy (glibc's log10f is not the optimised kernel).
93 return gain > T(0) ? std::max(minusInfinityDb, T(8.6858896380650365530) * std::log(gain))
94 : minusInfinityDb;
95}
96
97// ============================================================================
98// Interpolation and Mapping
99// ============================================================================
100
113template <FloatType T>
114[[nodiscard]] inline T mapRange(T value, T inMin, T inMax, T outMin, T outMax) noexcept
115{
116 if (inMin == inMax) return outMin; // Safety: prevent NaN/Inf in audio pipeline
117 return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin));
118}
119
133template <FloatType T>
134[[nodiscard]] inline T moveTowards(T from, T to, T maxDelta) noexcept
135{
136 // Return the target itself once within reach: from + (to - from) rounds
137 // off it in about 10% of float cases, leaving a settled ramp one ulp away.
138 const T delta = to - from;
139 return std::abs(delta) <= maxDelta ? to : from + std::clamp(delta, -maxDelta, maxDelta);
140}
141
142// ============================================================================
143// Fast Approximations
144// ============================================================================
145
160template <FloatType T>
161[[nodiscard]] inline T fastTanh(T x) noexcept
162{
163 // std::clamp translates to SIMD min/max intrinsics (no branching).
164 // Maintains continuity avoiding step-aliasing at the bounds.
165 x = std::clamp(x, T(-3), T(3));
166
167 const auto x2 = x * x;
168 const auto x4 = x2 * x2;
169 // Pade [5,4] approximant: max error < 0.05% for |x| <= 3
170 return x * (T(945) + T(105) * x2 + x4) / (T(945) + T(420) * x2 + T(15) * x4);
171}
172
182template <FloatType T>
183[[nodiscard]] inline T fastPow10(T x) noexcept
184{
185 // log2(10) folded into one constant: rounding it once is more accurate
186 // than multiplying log2(e) * ln(10) at runtime, and it saves a multiply
187 // (compilers may not reassociate x * a * b without fast-math).
188 constexpr T kLog2Of10 =
189 static_cast<T>(std::numbers::ln10_v<long double> / std::numbers::ln2_v<long double>);
190 return std::exp2(x * kLog2Of10);
191}
192
201template <FloatType T>
202[[nodiscard]] inline T fastExp(T x) noexcept
203{
204 return std::exp2(x * std::numbers::log2e_v<T>);
205}
206
221template <FloatType T>
222[[nodiscard]] inline T fastTan(T x) noexcept
223{
224 constexpr T limit = T(1.520); // ~pi/2 - 0.05
225 if (std::abs(x) > limit) return std::tan(x);
226 const T x2 = x * x;
227 const T x4 = x2 * x2;
228 // Pade [5,4]: tan(x) ~= x * (945 - 105*x^2 + x^4) / (945 - 420*x^2 + 15*x^4)
229 return x * (T(945) - T(105) * x2 + x4) / (T(945) - T(420) * x2 + T(15) * x4);
230}
231
247template <FloatType T>
248[[nodiscard]] inline T fastSin(T x) noexcept
249{
250 // Range-reduce to [-pi, pi] with a two-term Cody-Waite split of 2*pi so the
251 // subtraction stays accurate in float even for arguments several periods out.
252 const T k = std::floor(x * invTwoPi<T> + T(0.5));
253 x -= k * T(6.28125); // high part (exactly representable)
254 x -= k * T(0.0019353071795864769253); // low part of 2*pi
255
256 // Fold into [-pi/2, pi/2] where the polynomial converges fast:
257 // sin(x) = sin(pi - x) for x > pi/2 (and the odd mirror for x < -pi/2).
258 if (x > halfPi<T>) x = pi<T> - x;
259 else if (x < -halfPi<T>) x = -pi<T> - x;
260
261 const T x2 = x * x;
262 // Endpoint-constrained Remez minimax coefficients for sin on [-pi/2, pi/2]
263 // (equiripple error 3.73e-9, p(pi/2) == 1). Near-Taylor coefficients of
264 // the same degree leave ~3e-6 of error: a -110 dB harmonic floor.
265 constexpr auto c = detail::fastSinPolynomial<T>;
266 return x * (c[0] + x2 * (c[1] + x2 * (c[2] + x2 * (c[3] + x2 * c[4]))));
267}
268
275template <FloatType T>
276[[nodiscard]] inline T fastCos(T x) noexcept
277{
278 return fastSin(x + halfPi<T>);
279}
280
292template <FloatType T>
293[[nodiscard]] inline T fastLog(T x) noexcept
294{
295 int e = 0;
296 T m = std::frexp(x, &e); // x = m * 2^e, m in [0.5, 1)
297 // Normalise mantissa into [sqrt(0.5), sqrt(2)) so the series is centred.
298 if (m < T(0.70710678118654752440)) { m *= T(2); --e; }
299
300 // Classic atanh form (Cephes): ln(m) = 2*atanh(s), s = (m-1)/(m+1).
301 // |s| <= 0.1716 so the odd series converges below 1e-7 with 4 terms.
302 const T s = (m - T(1)) / (m + T(1));
303 const T s2 = s * s;
304 const T lnm = T(2) * s * (T(1)
305 + s2 * (T(1.0 / 3.0)
306 + s2 * (T(1.0 / 5.0)
307 + s2 * T(1.0 / 7.0))));
308
309 return lnm + static_cast<T>(e) * std::numbers::ln2_v<T>; // + e * ln2
310}
311
312// ============================================================================
313// Utility
314// ============================================================================
315
325template <FloatType T>
326[[nodiscard]] inline T wrapPhase(T phase) noexcept
327{
328 // floor() correctly handles negative values, preventing branching
329 T wrapped = phase - twoPi<T> * std::floor(phase * invTwoPi<T>);
330 // Rounding in the k * twoPi product can land wrapped just outside
331 // [0, 2*pi) on either side: large phases where the product overshoots
332 // the input leave a slightly negative result, and tiny negative phases
333 // can round the sum up to exactly twoPi. Enforce the documented
334 // half-open range. Order matters: the += branch can itself round up to
335 // exactly twoPi, which the second branch then folds to 0.
336 if (wrapped < T(0)) wrapped += twoPi<T>;
337 if (wrapped >= twoPi<T>) wrapped -= twoPi<T>;
338 return wrapped;
339}
340
341} // namespace dspark
Constrains a type to IEEE floating-point (float or double).
Definition DspMath.h:30
constexpr std::array< T, 5 > fastSinPolynomial
Definition DspMath.h:35
Main namespace for the DSPark framework.
constexpr T invSqrt2
1 / square root of 2 (0.70710...). Butterworth Q factor.
Definition DspMath.h:60
T fastTan(T x) noexcept
Fast approximation of tan(x) using a Pade [5,4] rational approximant.
Definition DspMath.h:222
T moveTowards(T from, T to, T maxDelta) noexcept
Moves a value toward a target by at most a given distance.
Definition DspMath.h:134
T fastPow10(T x) noexcept
Fast approximation of 10^x using exp2.
Definition DspMath.h:183
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
Definition DspMath.h:74
T mapRange(T value, T inMin, T inMax, T outMin, T outMax) noexcept
Maps a value from one range to another (linear interpolation).
Definition DspMath.h:114
constexpr T sqrt2
Square root of 2 (1.41421...).
Definition DspMath.h:57
constexpr T pi
Pi (3.14159...) for the given floating-point type.
Definition DspMath.h:45
T fastSin(T x) noexcept
Fast sine approximation (degree-9 odd minimax polynomial).
Definition DspMath.h:248
T fastCos(T x) noexcept
Fast cosine approximation. See fastSin() for accuracy notes (the pi/2 offset costs float about half a...
Definition DspMath.h:276
T fastLog(T x) noexcept
Fast natural logarithm approximation.
Definition DspMath.h:293
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
Definition DspMath.h:89
T fastTanh(T x) noexcept
Fast tanh approximation using Pade rational function.
Definition DspMath.h:161
constexpr T invTwoPi
1 / (2 * Pi) (0.15915...). Useful for fast phase divisions.
Definition DspMath.h:51
T fastExp(T x) noexcept
Fast approximation of e^x via std::exp2 (~2x faster than std::exp on MSVC).
Definition DspMath.h:202
constexpr T halfPi
Pi / 2 (1.57079...). Quarter period; sin/cos phase offset.
Definition DspMath.h:54
constexpr T twoPi
2 * Pi (6.28318...).
Definition DspMath.h:48
T wrapPhase(T phase) noexcept
Normalises a phase value to the range [0, 2*pi).
Definition DspMath.h:326