template<FloatType SampleType, size_t N,
windows::WindowType WindowType>
requires (N % 2 == 0)
class marvin::math::interpolators::WindowedSincInterpolator< SampleType, N, WindowType >
A windowed sinc interpolator, suitable for use in a realtime context.
Uses a lookup table precomputed at construction time for optimization purposes, and bakes the selected window function into the lookup table. For a non-integer subsample, the resulting point on the Sinc curve will be a lerp between the closest two points in the table. Input should contain N - 1
history
samples, followed by the latest sample. For example, in the case of N=4
, The input should be in the form [x[n-3], x[n-2], x[n-1], x[n]]
.
In the case of N=8
, it should be in the form [x[n-7], x[n-6], x[n-5], x[n-4], x[n-3], x[n-2], x[n-1], x[n]]
.
The interpolator will introduce N / 2
samples of latency (determined by assuming a fixed ratio of 0
, and counting the number of samples it takes an impulse to be output). For example, a signal [a, b, c, d, e, f, g, .......]
, with N=4
would be output as [0, 0, a, b, c, d ......]
.
To really belabour the point, the inputs passed to the interpolator for the above impulse and N=4
would be:
[0, 0, 0, a] => 0
[0, 0, a, b] => 0
[0, a, b, c] => a
[a, b, c, d] => b
[b, c, d, e] => c
[c, d, e, f] => d
....
Usage Example:
template<size_t N>
void upsample(std::span<float> source, double sourceSampleRate, std::vector<float>& dest, double desiredSampleRate) {
using namespace marvin::literals;
const auto originalLengthSeconds = static_cast<double>(source.size()) / sourceSampleRate;
const auto destSize = static_cast<size_t>(originalLengthSeconds * desiredSampleRate);
dest.resize(destSize);
const auto resamplingRatio = desiredSampleRate / sourceSampleRate;
const auto increment = 1.0 / resamplingRatio;
std::vector<float> resamplingBlock(N, 0.0f);
resamplingBlock[resamplingBlock.size()] = source[0];
marvin::math::WindowedSincInterpolator<float, N> interpolator{};
auto pos{ 0.0 }, prevPos{ 0.0 };
for(auto i = 0_sz; i < destSize; ++i) {
auto truncatedDelta = static_cast<int>(pos) - static_cast<int>(prevPos);
if(truncatedDelta >= 1) {
std::rotate(resamplingBlock.begin(), resamplingBlock.begin() + 1, resamplingBlock.end());
const auto next = source[static_cast<size_t>(pos)];
resamplingBlock[resamplingBlock.size() - 1] = next;
}
prevPos = pos;
const auto ratio = pos - std::floor(pos);
const auto resampled = interpolator.interpolate(resamplingBlock, ratio);
dest[i] = resampled;
pos += increment;
}
}