|
1 | 1 | # ReSinc |
2 | 2 | [](https://github.com/jedlamartin/ReSinc/actions/workflows/ci.yml) |
3 | 3 | [](https://opensource.org/licenses/MIT) |
4 | | - |
| 4 | + |
5 | 5 |
|
6 | 6 | `ReSinc` is a lightweight, real-time safe, header-only C++ library for audio oversampling. |
7 | 7 |
|
8 | | -It's designed for use in real-time audio applications (like VST/AU plugins) where running a non-linear process (distortion, saturation, etc.) at a higher sample rate is necessary to prevent aliasing artifacts. The library uses a pre-calculated, windowed Sinc filter for interpolation and decimation, ensuring high-fidelity results. |
| 8 | +It is designed for real-time audio applications (like VST/AU plugins) where running non-linear processes (distortion, saturation, compression) at a higher sample rate is necessary to prevent aliasing artifacts. The library uses a pre-calculated, windowed Sinc filter for high-fidelity interpolation and decimation. |
9 | 9 |
|
10 | 10 | ## Features |
11 | 11 |
|
12 | | -* **Real-Time Safe:** All memory is pre-allocated on initialization. The real-time processing functions (`interpolate`, `decimate`, `process`) are guaranteed not to allocate heap memory, lock, or block, making them safe for any high-priority audio thread. |
13 | | -* **High-Quality:** Uses a Kaiser-windowed Sinc filter for "perfect" reconstruction and anti-aliasing. |
14 | | -* **Simple Workflow:** Provides a clear `interpolate()` -> `process()` -> `decimate()` workflow. |
15 | | -* **Header-Only:** Just drop `ReSinc.hpp` into your project and include it. |
16 | | -* **Flexible & Template-Based:** Fully configurable via template parameters: |
| 12 | +* **Real-Time Safe:** All memory is pre-allocated during configuration. The real-time processing functions (`interpolate`, `decimate`, `process`) are guaranteed not to allocate heap memory, lock, or block. |
| 13 | +* **Flexible I/O:** Supports multiple data formats directly: |
| 14 | + * **Standard Containers:** `std::vector<float>`, `std::vector<std::vector<float>>`, `std::array`. |
| 15 | + * **JUCE Framework:** Directly accepts `juce::AudioBuffer<float>` / `juce::AudioBuffer<double>`. |
| 16 | + * **Raw Pointers:** Standard `float**` arrays. |
| 17 | +* **High-Quality:** Uses a Kaiser-windowed Sinc filter for near-perfect reconstruction. |
| 18 | +* **Header-Only:** Single file `ReSinc.hpp`. No linking required. |
| 19 | +* **Configurable:** |
17 | 20 | * `TYPE`: Sample type (`float` or `double`). |
18 | | - * `OVERSAMPLE_FACTOR`: The factor to oversample by (e.g., `2`, `4`, `8`). |
19 | | - * `SINC_RADIUS`: The "quality" of the filter, which also determines latency. |
| 21 | + * `OVERSAMPLE_FACTOR`: e.g., `2`, `4`, `8`, `16`. |
| 22 | + * `SINC_RADIUS`: Controls filter steepness and latency. |
20 | 23 |
|
21 | 24 | --- |
22 | 25 |
|
23 | 26 | ## Quick Start |
24 | 27 |
|
25 | | -Here is a minimal example of how to use `Oversampler` to apply 4x oversampling to a simple distortion effect. |
| 28 | +Here is a minimal example using `std::vector` to apply 4x oversampling to a simple distortion effect. |
26 | 29 |
|
27 | 30 | ```cpp |
28 | 31 | #include "ReSinc.hpp" |
29 | 32 | #include <vector> |
30 | 33 | #include <cmath> |
| 34 | +#include <algorithm> |
31 | 35 |
|
32 | 36 | // 1. Define your oversampler instance |
33 | 37 | // <float, 4x Oversampling, Sinc Radius of 32> |
34 | 38 | Oversampler<float, 4, 32> oversampler; |
35 | 39 |
|
36 | 40 | // 2. Call configure() once before processing begins |
37 | | -// (e.g., in your plugin's prepareToPlay()) |
38 | 41 | void setupAudio() { |
39 | 42 | float sampleRate = 44100.0f; |
40 | 43 | int maxChannels = 2; |
41 | | - int maxBlockSize = 1024; |
| 44 | + int maxBlockSize = 512; |
42 | 45 |
|
43 | 46 | oversampler.configure(sampleRate, maxChannels, maxBlockSize); |
44 | 47 | } |
45 | 48 |
|
46 | | -// 3. Run in your real-time audio callback (e.g., processBlock()) |
47 | | -void processAudio(const float* const* input, float* const* output, int numChannels, int numSamples) { |
| 49 | +// 3. Run in your real-time audio loop |
| 50 | +void processBlock(std::vector<std::vector<float>>& input, |
| 51 | + std::vector<std::vector<float>>& output) { |
48 | 52 |
|
49 | | - // 3a. Upsample the audio into the internal buffer |
50 | | - oversampler.interpolate(input, numChannels, numSamples); |
| 53 | + // 3a. Upsample: Input -> Internal Buffer |
| 54 | + // (Supports std::vector, juce::AudioBuffer, or float**) |
| 55 | + oversampler.interpolate(input); |
51 | 56 |
|
52 | | - // 3b. Process the high-resolution internal buffer |
53 | | - // This lambda is called immediately. |
54 | | - oversampler.process([&](std::vector<std::vector<float>>& internalBuffer) { |
| 57 | + // 3b. Process: Operate on the high-resolution buffer |
| 58 | + // The callback receives the upsampled data (4x larger size) |
| 59 | + oversampler.process([&](std::vector<std::vector<float>>& upsampledData) { |
55 | 60 | |
56 | | - // 'internalBuffer' is now 4x larger |
57 | | - int oversampledSamples = numSamples * 4; |
58 | | - |
59 | | - for (int ch = 0; ch < numChannels; ++ch) { |
60 | | - for (int s = 0; s < oversampledSamples; ++s) { |
61 | | - // Apply a non-linear process (e.g., distortion) |
62 | | - // This would alias badly at 1x, but is safe at 4x |
63 | | - internalBuffer[ch][s] = std::tanh(internalBuffer[ch][s] * 2.0f); |
| 61 | + for (auto& channel : upsampledData) { |
| 62 | + for (auto& sample : channel) { |
| 63 | + // Apply non-linear process (e.g., Hard Clip) |
| 64 | + // Safe from aliasing because we are at 4x rate |
| 65 | + sample = std::max(-1.0f, std::min(1.0f, sample * 1.5f)); |
64 | 66 | } |
65 | 67 | } |
66 | 68 | }); |
67 | 69 |
|
68 | | - // 3c. Downsample back to the original rate. |
69 | | - // The Sinc filter automatically anti-aliases the signal. |
70 | | - oversampler.decimate(output, numChannels, numSamples); |
| 70 | + // 3c. Downsample: Internal Buffer -> Output |
| 71 | + // Automatically applies anti-aliasing filter |
| 72 | + oversampler.decimate(output); |
71 | 73 | } |
72 | 74 | ``` |
73 | 75 | ## API |
74 | 76 |
|
75 | 77 | ### Template Parameters |
76 | 78 | `template<typename TYPE, int OVERSAMPLE_FACTOR, int SINC_RADIUS>` |
77 | | -* **`TYPE`**: The sample type to use, e.g., `float` or `double`. |
78 | | -* **`OVERSAMPLE_FACTOR`**: The interpolation factor. `4` means 4x oversampling. |
79 | | -* **`SINC_RADIUS`**: The half-length of the Sinc filter. This controls the filter's quality. A good starting value is `32`. Higher values mean better anti-aliasing but higher CPU cost and latency. |
| 79 | +* **`TYPE`**: The sample type (`float` or `double`). |
| 80 | +* **`OVERSAMPLE_FACTOR`**: Integer factor (e.g., `2`, `4`, `8`). |
| 81 | +* **`SINC_RADIUS`**: Half-length of the Sinc filter. |
| 82 | + * Example: `32` taps per side. |
| 83 | + * Latency = `2 * SINC_RADIUS` samples (at input rate). |
80 | 84 |
|
81 | | -### Public Methods |
| 85 | +### Configuration |
82 | 86 |
|
83 | | -* **`void configure(TYPE sampleRate, int maxChannels, int maxBlockSize)`** |
84 | | - Pre-allocates all internal memory. This **must** be called once from a non-real-time thread before any processing begins. |
| 87 | +#### `void configure(TYPE sampleRate, int maxChannels, int maxBlockSize)` |
| 88 | +Pre-allocates all internal memory. **Must** be called once (from a non-real-time thread) before processing. |
85 | 89 |
|
86 | | -* **`void interpolate(const TYPE* const* ptrToBuffers, int numChannels, int numSamples)`** |
87 | | - Upsamples a block of audio from `ptrToBuffers` into the internal high-sample-rate buffer. |
| 90 | +### Real-Time Processing |
88 | 91 |
|
89 | | -* **`void process(std::function<void(std::vector<std::vector<TYPE>>&)> processBlock)`** |
90 | | - Takes a lambda (or other `std::function`) and executes it, giving it direct mutable access to the internal high-sample-rate buffer. This is where you should apply your oversampled processing. |
| 92 | +The `interpolate`, `process`, and `decimate` methods provide overloads for three categories of data: |
91 | 93 |
|
92 | | -* **`void decimate(TYPE* const* ptrToBuffers, int numChannels, int numSamples)`** |
93 | | - Downsamples the internal high-sample-rate buffer into the provided `ptrToBuffers`, automatically applying the anti-aliasing filter. |
| 94 | +1. **JUCE Types:** `juce::AudioBuffer<T>` |
| 95 | +2. **Standard Containers:** `std::vector<T>`, `std::vector<std::vector<T>>` |
| 96 | +3. **Raw Pointers:** `T**` / `T* const*` |
94 | 97 |
|
95 | | - --- |
| 98 | +#### 1. Interpolate (Input -> Internal) |
| 99 | +Upsamples input data and fills the internal buffer. |
96 | 100 |
|
| 101 | +```cpp |
| 102 | +// JUCE Support |
| 103 | +void interpolate(const juce::AudioBuffer<TYPE>& buffer); |
| 104 | +
|
| 105 | +// STL Support (Multi-channel & Single-channel) |
| 106 | +void interpolate(const std::vector<std::vector<TYPE>>& buffer); |
| 107 | +void interpolate(const std::vector<TYPE>& buffer); |
| 108 | +
|
| 109 | +// Raw Pointer Support |
| 110 | +void interpolate(const TYPE* const* ptrToBuffers, int numChannels, int numSamples); |
| 111 | +``` |
| 112 | +#### 2. Process (Callback) |
| 113 | +Provides direct access to the upsampled data via a callback/lambda. |
| 114 | + |
| 115 | +```cpp |
| 116 | +// 1. Vector Access (Most Common) |
| 117 | +// Provides the internal buffer as a vector of vectors |
| 118 | +oversampler.process([](std::vector<std::vector<TYPE>>& data) { ... }); |
| 119 | + |
| 120 | +// 2. JUCE Wrapper Access |
| 121 | +// Wraps the internal pointers in a temporary AudioBuffer for convenience. |
| 122 | +// (Only enables if passed a JUCE type) |
| 123 | +oversampler.process([](juce::AudioBuffer<TYPE> data) { ... }); |
| 124 | + |
| 125 | +// 3. Per-Channel Access |
| 126 | +// Calls your lambda once for each channel vector |
| 127 | +oversampler.process([](std::vector<TYPE>& channelData) { ... }); |
| 128 | +``` |
| 129 | +#### 3. Decimate (Internal -> Output) |
| 130 | +Downsamples the internal buffer and writes to the output. |
| 131 | +
|
| 132 | +```cpp |
| 133 | +// JUCE Support |
| 134 | +void decimate(juce::AudioBuffer<TYPE>& buffer); |
| 135 | +
|
| 136 | +// STL Support |
| 137 | +void decimate(std::vector<std::vector<TYPE>>& buffer); |
| 138 | +void decimate(std::vector<TYPE>& buffer); |
| 139 | +
|
| 140 | +// Raw Pointer Support |
| 141 | +void decimate(TYPE* const* ptrToBuffers, int numChannels, int numSamples); |
| 142 | +``` |
97 | 143 | ## Latency |
98 | 144 |
|
99 | 145 | This library uses a linear-phase (symmetric) Sinc filter. By design, this introduces a known, fixed processing latency. |
@@ -127,8 +173,6 @@ The repository includes a `tests/tests.cpp` file for validation. You can compile |
127 | 173 | g++ tests/tests.cpp -o run_tests -std=c++17 -O3 |
128 | 174 | ./run_tests |
129 | 175 | ``` |
130 | | ---- |
131 | | - |
132 | 176 | ## License |
133 | 177 |
|
134 | 178 | This project is licensed under the **MIT License**. See the `LICENSE` file for details. |
0 commit comments