Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
fast_math.hpp
Go to the documentation of this file.
1// sbl/dsp/fast_math.hpp — Fast analytical approximations (Audio Stack — Atoms)
2//
3// Lightweight math functions for hot audio paths where libm functions
4// like sinf() are too expensive. All functions are pure arithmetic —
5// no lookup tables, no flash cost.
6//
7// fast_sinf(x): 5th-order Taylor series for sin(x), valid for x in
8// [0, π/2]. Max error < 0.00016 at x = π/2. ~5 cycles on M7 FPU
9// vs ~500 cycles for newlib sinf().
10//
11// fast_exp2f(x): Fast 2^x for exponential modulation (1V/oct-style).
12// Integer part via IEEE 754 exponent shift, fractional part via
13// 3rd-order minimax polynomial. ~8 cycles on M7 FPU. Accurate to
14// ~20 bits for |x| <= 4 (±4 octaves).
15//
16// Usage:
17// float w = pi * fc / (2.0f * fs);
18// float f = 2.0f * sbl::dsp::math::fast_sinf(w); // SVF coefficient
19//
20// // Exponential modulation (LFO → filter cutoff):
21// float mod_cutoff = cutoff * sbl::dsp::math::fast_exp2f(lfo * depth_oct);
22
23#ifndef SBL_DSP_MATH_FAST_MATH_HPP_
24#define SBL_DSP_MATH_FAST_MATH_HPP_
25
27
28namespace sbl::dsp::math {
29
30/// Fast sine approximation for x in [0, π/2]
31///
32/// Uses 5th-order Taylor series: x - x³/6 + x⁵/120
33///
34/// Accuracy:
35/// x = 0.0 → error = 0
36/// x = 0.589 → error < 0.00016 (SVF max at 18 kHz / 48 kHz)
37/// x = π/2 → error ≈ 0.00045 (theoretical max in domain)
38///
39/// @param x Input angle in radians, must be in [0, π/2]
40/// @return Approximation of sin(x)
41inline float fast_sinf(float x) {
42 float x2 = x * x;
43 return x * (1.0f - x2 * (1.0f / 6.0f - x2 * (1.0f / 120.0f)));
44}
45
46/// Fast tan(π·f) for normalized frequency f ∈ [0, 0.497]
47///
48/// [5,4] Padé approximant of tan(x) evaluated at x = π·f:
49///
50/// tan(x) ≈ x · (945 - 105x² + x⁴) / (945 - 420x² + 15x⁴)
51///
52/// Unlike a polynomial, the Padé rational function correctly models the
53/// pole at f = 0.5 (Nyquist). The denominator goes to zero at x = π/2,
54/// matching the true singularity of tan.
55///
56/// Accuracy vs true tan(π·f):
57/// f = 0.10 (4.8 kHz) → error < 0.001%
58/// f = 0.35 (16.8 kHz) → error < 0.3%
59/// f = 0.45 (21.6 kHz) → error < 2.4%
60/// f = 0.497 (23.9 kHz) → error < 0.1%
61///
62/// The previous 5th-order polynomial (from MI stmlib) diverged badly
63/// above f ≈ 0.35, giving 45% error at f = 0.45 — causing audible
64/// artifacts (secondary resonant peaks) in the ZDF SVF.
65///
66/// Cost: ~8 FMA + 1 VDIV ≈ 20–25 cycles on M7 FPU (vs ~5 for the old
67/// polynomial, vs ~50–100 for newlib tanf).
68///
69/// @param f Normalized frequency (freq_hz / sample_rate), must be < 0.497
70/// @return Approximation of tan(π·f)
71inline float fast_tan_pif(float f) {
72 constexpr float pi = PI;
73 constexpr float pi2 = pi * pi;
74 constexpr float pi4 = pi2 * pi2;
75 float f2 = f * f;
76 float f4 = f2 * f2;
77 float num = 945.0f - 105.0f * pi2 * f2 + pi4 * f4;
78 float den = 945.0f - 420.0f * pi2 * f2 + 15.0f * pi4 * f4;
79 return pi * f * num / den;
80}
81
82
83/// Fast 2^x approximation for exponential modulation
84///
85/// Decomposes x into integer and fractional parts. The integer part is
86/// applied by shifting the IEEE 754 exponent field (exact). The fractional
87/// part uses a 3rd-order minimax polynomial (accurate to ~20 bits).
88///
89/// This is the "exponential converter" primitive — the analog equivalent
90/// of the circuit that makes 1V/oct work in a VCF or VCO.
91///
92/// Accuracy (vs std::exp2f):
93/// |x| <= 1 → max relative error < 0.02%
94/// |x| <= 4 → max relative error < 0.05%
95/// |x| > 16 → clamped (returns 0 for x < -16)
96///
97/// @param x Exponent (e.g., ±2.0 for ±2 octave modulation)
98/// @return Approximation of 2^x
99inline float fast_exp2f(float x) {
100 // Clamp to prevent overflow/underflow
101 if (x < -16.0f) return 0.0f;
102 if (x > 16.0f) x = 16.0f;
103
104 // Decompose into integer and fractional parts
105 // Use truncation toward negative infinity
106 int i = static_cast<int>(x);
107 float f = x - static_cast<float>(i);
108 if (f < 0.0f) { f += 1.0f; --i; }
109
110 // 4th-order polynomial for 2^f, f in [0, 1)
111 // Remez-style minimax coefficients for improved accuracy over Taylor
112 constexpr float C1 = 0.6931472f; // ln(2)
113 constexpr float C2 = 0.2402265f; // ln(2)^2 / 2 (approx)
114 constexpr float C3 = 0.0558011f; // ln(2)^3 / 6 (approx)
115 constexpr float C4 = 0.00898f; // ln(2)^4 / 24 (approx)
116 float p = 1.0f + f * (C1 + f * (C2 + f * (C3 + f * C4)));
117
118 // Apply integer exponent via IEEE 754 bit manipulation
119 union { float fv; int32_t iv; } v;
120 v.fv = p;
121 v.iv += i << 23;
122 return v.fv;
123}
124
125/// Fast tanh approximation using [3,2] Padé approximant
126///
127/// tanh(x) ≈ x · (27 + x²) / (27 + 9·x²) for |x| ≤ 3
128///
129/// Same rational function as SoftLimiter::limit() in soft_limiter.hpp, but with
130/// input clamping and NaN guard. SoftLimiter is deliberately unbounded
131/// (approaches x/9 for large x); this function saturates to ±1.
132///
133/// Accurate to < 1% relative error for |x| < 3, which covers the
134/// operating range of the Moog ladder filter (audio signals + moderate
135/// drive + feedback). Inputs outside ±3 are clamped to ±1.
136///
137/// Cost: ~8 cycles on M7 FPU (2 MUL, 2 FMA, 1 VDIV).
138///
139/// @param x Input value (any range, clamped for |x| > 3)
140/// @return Approximation of tanh(x)
141inline float fast_tanhf(float x) {
142 if (x < -3.0f) return -1.0f;
143 if (x > 3.0f) return 1.0f;
144 if (x != x) return 0.0f; // NaN → 0 (defense in depth)
145 // [3,2] Padé: coefficients 27 = 3^3 and 9 = 3^2
146 float x2 = x * x;
147 return x * (27.0f + x2) / (27.0f + 9.0f * x2);
148}
149
150/// A unipolar signal to a frequency, exponentially: min_hz at 0, min_hz · 2^octaves at 1.
151/// The one su-to-Hz map for every cutoff and damping port.
152inline float su_to_hz(float su, float min_hz, float octaves) {
153 return min_hz * fast_exp2f(su * octaves);
154}
155
156/// 1 − e^(−x) by inverse Taylor, for a one-pole coefficient from a time
157/// constant or a cutoff (x = 3/(τ·rate), 4.6/n, ω). Cold path; no expf.
158inline constexpr float one_minus_exp_neg(float x) {
159 const float denom = 1.0f + x * (1.0f + x * (0.5f + x * (1.0f / 6.0f)));
160 return 1.0f - 1.0f / denom;
161}
162
163} // namespace sbl::dsp::math
164
165#endif // SBL_DSP_MATH_FAST_MATH_HPP_
The numbers every layer reaches for.
Cross-cutting math used at every audio layer.
Definition clamp.hpp:10
constexpr float PI
Definition constants.hpp:11
float fast_sinf(float x)
Definition fast_math.hpp:41
float fast_exp2f(float x)
Definition fast_math.hpp:99
float fast_tan_pif(float f)
Definition fast_math.hpp:71
constexpr float one_minus_exp_neg(float x)
float su_to_hz(float su, float min_hz, float octaves)
float fast_tanhf(float x)