|
| 1 | +/* |
| 2 | + * StreamingResampler.h |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-3-Clause |
| 5 | + * |
| 6 | + * Copyright (C) 2026 brummer <brummer@web.de> |
| 7 | + */ |
| 8 | + |
| 9 | +#pragma once |
| 10 | + |
| 11 | +#include <cstdint> |
| 12 | +#include <vector> |
| 13 | +#include <cmath> |
| 14 | +#include <cstring> |
| 15 | + |
| 16 | + |
| 17 | +class StreamingResampler { |
| 18 | +public: |
| 19 | + StreamingResampler() = default; |
| 20 | + |
| 21 | + void setup(uint32_t channels, uint32_t maxBlockSize, uint32_t fs_in, uint32_t fs_out) { |
| 22 | + chan = channels; |
| 23 | + ratio = double(fs_in) / double(fs_out); |
| 24 | + bufferSize = maxBlockSize + 8; |
| 25 | + buffer.resize(bufferSize * chan, 0.0f); |
| 26 | + reset(); |
| 27 | + } |
| 28 | + |
| 29 | + void reset() { |
| 30 | + readPos = 0.0; |
| 31 | + buffered = 0; |
| 32 | + std::fill(buffer.begin(), buffer.end(), 0.0f); |
| 33 | + } |
| 34 | + |
| 35 | + void setSampleRates(uint32_t fs_in, uint32_t fs_out) { |
| 36 | + ratio = double(fs_in) / double(fs_out); |
| 37 | + } |
| 38 | + |
| 39 | + uint32_t getOutSize(uint32_t inFrames) const { |
| 40 | + return (uint32_t)std::ceil(inFrames / ratio); |
| 41 | + } |
| 42 | + |
| 43 | + uint32_t resample(const float* input, float* output, uint32_t inFrames) { |
| 44 | + append(input, inFrames); |
| 45 | + uint32_t outFrames = 0; |
| 46 | + while (true) { |
| 47 | + int ip = (int)readPos; |
| 48 | + if (ip + 2 >= (int)buffered) break; |
| 49 | + float t = float(readPos - ip); |
| 50 | + |
| 51 | + for (uint32_t ch = 0; ch < chan; ++ch) { |
| 52 | + float x0 = sample(ip - 1, ch); |
| 53 | + float x1 = sample(ip, ch); |
| 54 | + float x2 = sample(ip + 1, ch); |
| 55 | + float x3 = sample(ip + 2, ch); |
| 56 | + output[outFrames * chan + ch] = hermite(x0, x1, x2, x3, t); |
| 57 | + } |
| 58 | + readPos += ratio; |
| 59 | + ++outFrames; |
| 60 | + } |
| 61 | + |
| 62 | + int consumed = (int)readPos; |
| 63 | + |
| 64 | + if (consumed > 0) { |
| 65 | + shiftBuffer(consumed); |
| 66 | + readPos -= consumed; |
| 67 | + } |
| 68 | + |
| 69 | + return outFrames; |
| 70 | + } |
| 71 | + |
| 72 | +private: |
| 73 | + uint32_t chan = 0; |
| 74 | + double ratio = 1.0; |
| 75 | + std::vector<float> buffer; |
| 76 | + uint32_t bufferSize = 0; |
| 77 | + uint32_t buffered = 0; |
| 78 | + double readPos = 0.0; |
| 79 | + |
| 80 | + void append(const float* input, uint32_t frames) { |
| 81 | + for (uint32_t i = 0; i < frames; ++i) { |
| 82 | + for (uint32_t ch = 0; ch < chan; ++ch) { |
| 83 | + buffer[(buffered + i) * chan + ch] = |
| 84 | + input[i * chan + ch]; |
| 85 | + } |
| 86 | + } |
| 87 | + buffered += frames; |
| 88 | + } |
| 89 | + |
| 90 | + void shiftBuffer(uint32_t frames) { |
| 91 | + uint32_t remaining = buffered - frames; |
| 92 | + |
| 93 | + std::memmove(buffer.data(), buffer.data() + frames * chan, remaining * chan * sizeof(float)); |
| 94 | + |
| 95 | + buffered = remaining; |
| 96 | + } |
| 97 | + |
| 98 | + inline float sample(int idx, uint32_t ch) const { |
| 99 | + if (idx < 0) return buffer[ch]; |
| 100 | + |
| 101 | + if ((uint32_t)idx >= buffered) |
| 102 | + return buffer[(buffered - 1) * chan + ch]; |
| 103 | + |
| 104 | + return buffer[idx * chan + ch]; |
| 105 | + } |
| 106 | + |
| 107 | + static inline float hermite(float x0, float x1, float x2, float x3, float t) { |
| 108 | + float c0 = x1; |
| 109 | + float c1 = 0.5f * (x2 - x0); |
| 110 | + float c2 = x0 - 2.5f * x1 + 2.0f * x2 - 0.5f * x3; |
| 111 | + float c3 = 0.5f * (x3 - x0) + 1.5f * (x1 - x2); |
| 112 | + return ((c3*t + c2)*t + c1)*t + c0; |
| 113 | + } |
| 114 | +}; |
0 commit comments