DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Interpolation.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
43#include "DspMath.h"
44
45#include <cassert>
46#include <cmath>
47
48namespace dspark {
49
58template <FloatType T>
59[[nodiscard]] inline T interpolateLinear(T y0, T y1, T frac) noexcept {
60 return y0 + frac * (y1 - y0);
61}
62
77template <FloatType T>
78[[nodiscard]] inline T interpolateLinear(const T* buffer, int length, T position) noexcept {
79 assert(length > 0 && position >= T(0) && position < static_cast<T>(length));
80 if (length <= 0) return T(0);
81
82 // An out-of-range position (including NaN) would make the int cast below
83 // undefined and the buffer reads out of bounds: clamp before casting.
84 int idx0;
85 T frac;
86 if (!(position >= T(0))) { idx0 = 0; frac = T(0); }
87 else if (position >= static_cast<T>(length)) { idx0 = length - 1; frac = T(0); }
88 else {
89 idx0 = static_cast<int>(position);
90 frac = position - static_cast<T>(idx0);
91 }
92
93 int idx1 = idx0 + 1;
94 if (idx1 >= length) idx1 = 0;
95
96 return interpolateLinear(buffer[idx0], buffer[idx1], frac);
97}
98
112template <FloatType T>
113[[nodiscard]] inline T interpolateHermite(T y0, T y1, T y2, T y3, T frac) noexcept {
114 T c = (y2 - y0) * T(0.5);
115 T v = y1 - y2;
116 T w = c + v;
117 T a = w + v + (y3 - y1) * T(0.5);
118 T b = w + a;
119 return (((a * frac) - b) * frac + c) * frac + y1;
120}
121
137template <FloatType T>
138[[nodiscard]] inline T interpolateHermite(const T* buffer, int length, T position) noexcept {
139 assert(length > 0 && position >= T(0) && position < static_cast<T>(length));
140 if (length <= 0) return T(0);
141
142 // Same release-safe clamp as the linear overload (undefined int cast /
143 // out-of-bounds reads otherwise).
144 int idx1;
145 T frac;
146 if (!(position >= T(0))) { idx1 = 0; frac = T(0); }
147 else if (position >= static_cast<T>(length)) { idx1 = length - 1; frac = T(0); }
148 else {
149 idx1 = static_cast<int>(position);
150 frac = position - static_cast<T>(idx1);
151 }
152
153 int idx0 = (idx1 > 0) ? idx1 - 1 : length - 1;
154 int idx2 = idx1 + 1; if (idx2 >= length) idx2 -= length;
155 int idx3 = idx2 + 1; if (idx3 >= length) idx3 -= length;
156
157 return interpolateHermite(buffer[idx0], buffer[idx1], buffer[idx2], buffer[idx3], frac);
158}
159
164template <FloatType T>
165[[nodiscard]] inline T interpolateCubic(const T* buffer, int length, T position) noexcept {
166 return interpolateHermite(buffer, length, position);
167}
168
182template <FloatType T>
183[[nodiscard]] inline T interpolateLagrange(T y0, T y1, T y2, T y3, T frac) noexcept {
184 T d = frac;
185 T dm1 = d - T(1);
186 T dm2 = d - T(2);
187 T dp1 = d + T(1);
188
189 // Precomputed division multipliers: 1/6 ~ 0.16666667, 1/2 = 0.5
190 static constexpr T inv6 = T(1.0 / 6.0);
191 static constexpr T inv2 = T(0.5);
192
193 T l0 = -(d * dm1 * dm2) * inv6;
194 T l1 = (dp1 * dm1 * dm2) * inv2;
195 T l2 = -(dp1 * d * dm2) * inv2;
196 T l3 = (dp1 * d * dm1) * inv6;
197
198 return l0 * y0 + l1 * y1 + l2 * y2 + l3 * y3;
199}
200
216template <FloatType T>
217[[nodiscard]] inline T interpolateLagrange(const T* buffer, int length, T position) noexcept {
218 assert(length > 0 && position >= T(0) && position < static_cast<T>(length));
219 if (length <= 0) return T(0);
220
221 // Same release-safe clamp as the linear overload (undefined int cast /
222 // out-of-bounds reads otherwise).
223 int idx1;
224 T frac;
225 if (!(position >= T(0))) { idx1 = 0; frac = T(0); }
226 else if (position >= static_cast<T>(length)) { idx1 = length - 1; frac = T(0); }
227 else {
228 idx1 = static_cast<int>(position);
229 frac = position - static_cast<T>(idx1);
230 }
231
232 int idx0 = (idx1 > 0) ? idx1 - 1 : length - 1;
233 int idx2 = idx1 + 1; if (idx2 >= length) idx2 -= length;
234 int idx3 = idx2 + 1; if (idx3 >= length) idx3 -= length;
235
236 return interpolateLagrange(buffer[idx0], buffer[idx1], buffer[idx2], buffer[idx3], frac);
237}
238
265template <FloatType T>
266[[nodiscard]] inline T interpolateAllpass(T currentSample, T previousSample,
267 T frac, T& state) noexcept
268{
269 // frac == 0 would place the pole exactly at z = -1 (a marginally stable
270 // resonator at Nyquist) and frac == -1 would divide by zero: clamp away
271 // from both. The clamp changes the delivered delay only for out-of-range
272 // requests.
273 T safeFrac = (frac < T(0.001)) ? T(0.001) : frac;
274 T coeff = (T(1) - safeFrac) / (T(1) + safeFrac);
275
276 T output = coeff * (currentSample - state) + previousSample;
277
278 // Cut the recursive tail below ~-300 dBFS so a decaying state can never
279 // reach the denormal range (matters on targets without FTZ/DAZ).
280 if (std::abs(output) < T(1e-15)) output = T(0);
281
282 state = output;
283 return output;
284}
285
286} // namespace dspark
Core mathematical utilities for digital signal processing.
Main namespace for the DSPark framework.
T interpolateCubic(const T *buffer, int length, T position) noexcept
Alias of interpolateHermite (Catmull-Rom evaluated in Hermite form). Kept for backward compatibility.
T interpolateHermite(T y0, T y1, T y2, T y3, T frac) noexcept
4-point, 3rd-order Hermite interpolation (optimized x-form).
T interpolateLinear(T y0, T y1, T frac) noexcept
Linear interpolation between two adjacent samples.
T interpolateLagrange(T y0, T y1, T y2, T y3, T frac) noexcept
4-point Lagrange interpolation from discrete samples.
T interpolateAllpass(T currentSample, T previousSample, T frac, T &state) noexcept
Allpass interpolation (first-order Thiran) for fractional delay.