iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
RealtimeResampler.h
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11#pragma once
12
13#include <functional>
14#include <cmath>
15
16#include "IPlugPlatform.h"
17#include "LanczosResampler.h"
18
19#include "heapbuf.h"
20#include "ptrlist.h"
21
22BEGIN_IPLUG_NAMESPACE
23
43template<typename T = double, int NCHANS=2, size_t A=12>
45{
46 static_assert(std::is_same<T, float>::value || std::is_same<T, double>::value, "T must be float or double");
47
48public:
49 enum class ESRCMode
50 {
51 kLinearInterpolation = 0,
52 kLancsoz,
53 kNumResamplingModes
54 };
55
56 using BlockProcessFunc = std::function<void(T**, T**, int, int)>;
57 using LanczosResampler = LanczosResampler<T, NCHANS, A>;
58
63 RealtimeResampler(double innerSampleRate, ESRCMode mode = ESRCMode::kLancsoz)
64 : mResamplingMode(mode)
65 , mInnerSampleRate(innerSampleRate)
66 {
67 }
68
69 RealtimeResampler(const RealtimeResampler&) = delete;
70 RealtimeResampler& operator=(const RealtimeResampler&) = delete;
71
76 void Reset(double inputSampleRate, int maxBlockSize = DEFAULT_BLOCK_SIZE)
77 {
78 mOuterSampleRate = inputSampleRate;
79 mInRatio = mOuterSampleRate / mInnerSampleRate;
80 mOutRatio = mInnerSampleRate / mOuterSampleRate;
81 mMaxOuterLength = maxBlockSize;
82 mMaxInnerLength = CalculateMaxInnerLength(mMaxOuterLength);
83
84 mInputData.Resize(mMaxInnerLength * NCHANS);
85 mOutputData.Resize(mMaxInnerLength * NCHANS);
86 mInputPtrs.Empty();
87 mOutputPtrs.Empty();
88
89 for (auto chan=0; chan<NCHANS; chan++)
90 {
91 mInputPtrs.Add(mInputData.Get() + (chan * mMaxInnerLength));
92 mOutputPtrs.Add(mOutputData.Get() + (chan * mMaxInnerLength));
93 }
94
95 ClearBuffers();
96
97 if (mResamplingMode == ESRCMode::kLancsoz)
98 {
99 const T outerRate = static_cast<T>(mOuterSampleRate);
100 const T innerRate = static_cast<T>(mInnerSampleRate);
101 mInResampler = std::make_unique<LanczosResampler>(outerRate, innerRate);
102 mOutResampler = std::make_unique<LanczosResampler>(innerRate, outerRate);
103
104 // Warm up the resamplers with enough silence that the first real buffer can yield the required number of output samples.
105 const auto outSamplesRequired = mOutResampler->GetNumSamplesRequiredFor(1);
106 const auto inSamplesRequired = mInResampler->GetNumSamplesRequiredFor(outSamplesRequired);
107 mInResampler->PushBlock(mInputPtrs.GetList(), inSamplesRequired, NCHANS);
108 const auto populated = mInResampler->PopBlock(mInputPtrs.GetList(), outSamplesRequired, NCHANS);
109 assert(populated >= outSamplesRequired && "Didn't get enough samples required for warm up!");
110 mOutResampler->PushBlock(mOutputPtrs.GetList(), populated, NCHANS);
111
112 // magic number that we seem to need to align when compensating for latency
113 constexpr auto addedLatency = 2;
114 mLatency = static_cast<int>(inSamplesRequired + addedLatency);
115 }
116 else
117 {
118 mLatency = 0;
119 }
120 }
121
128 void ProcessBlock(T** inputs, T** outputs, int nFrames, int nChans, BlockProcessFunc func)
129 {
130 if (mInnerSampleRate == mOuterSampleRate) // nothing to do!
131 {
132 func(inputs, outputs, nFrames, nChans);
133 return;
134 }
135
136 switch (mResamplingMode)
137 {
138 case ESRCMode::kLinearInterpolation:
139 {
140 const auto nNewFrames = LinearInterpolate(inputs, mInputPtrs.GetList(), nFrames, nChans, mInRatio, mMaxInnerLength);
141 func(mInputPtrs.GetList(), mOutputPtrs.GetList(), nNewFrames, nChans);
142 LinearInterpolate(mOutputPtrs.GetList(), outputs, nNewFrames, nChans, mOutRatio, nFrames);
143 break;
144 }
145 case ESRCMode::kLancsoz:
146 {
147 mInResampler->PushBlock(inputs, nFrames, nChans);
148 const auto maxInnerLength = CalculateMaxInnerLength(nFrames);
149
150 while (mInResampler->GetNumSamplesRequiredFor(1) == 0) // i.e. there's signal still available to pop
151 {
152 const auto populated = mInResampler->PopBlock(mInputPtrs.GetList(), maxInnerLength, nChans);
153 assert(populated <= maxInnerLength && "Received more samples than the encapsulated DSP is able to handle!");
154 func(mInputPtrs.GetList(), mOutputPtrs.GetList(), static_cast<int>(populated), nChans);
155 mOutResampler->PushBlock(mOutputPtrs.GetList(), populated, nChans);
156 }
157
158#ifdef _DEBUG
159 const auto populated =
160#endif
161 mOutResampler->PopBlock(outputs, nFrames, nChans);
162 assert(populated >= nFrames && "Did not yield enough samples to provide the required output buffer!");
163
164 mInResampler->RenormalizePhases();
165 mOutResampler->RenormalizePhases();
166 break;
167 }
168 default:
169 break;
170 }
171 }
172
174 int GetLatency() const { return mLatency; }
175
176private:
178 static inline int LinearInterpolate(T** inputs, T** outputs, int inputLength, int nChans, double ratio, int maxOutputLength)
179 {
180 const auto outputLength =
181 std::min(static_cast<int>(std::ceil(static_cast<double>(inputLength) / ratio)), maxOutputLength);
182
183 for (auto writePos=0; writePos<outputLength; writePos++)
184 {
185 const auto readPos = ratio * static_cast<double>(writePos);
186 const auto readPostionTrunc = std::floor(readPos);
187 const auto readPosInt = static_cast<int>(readPostionTrunc);
188
189 if (readPosInt < inputLength)
190 {
191 const T y = static_cast<T>(readPos - readPostionTrunc); // Cast to T to avoid precision warning
192
193 for (auto chan=0; chan<nChans; chan++)
194 {
195 const T x0 = inputs[chan][readPosInt];
196 const T x1 = ((readPosInt + 1) < inputLength) ? inputs[chan][readPosInt + 1]
197 : inputs[chan][readPosInt - 1];
198 outputs[chan][writePos] = (T(1) - y) * x0 + y * x1;
199 }
200 }
201 }
202
203 return outputLength;
204 }
205
207 void ClearBuffers()
208 {
209 const auto nBytes = mMaxInnerLength * NCHANS * sizeof(T);
210 memset(mInputData.Get(), 0, nBytes);
211 memset(mOutputData.Get(), 0, nBytes);
212 }
213
215 int CalculateMaxInnerLength(const int outerLength) const
216 {
217 return static_cast<int>(std::ceil(double(outerLength) / mInRatio));
218 }
219
220 WDL_TypedBuf<T> mInputData, mOutputData;
221 WDL_PtrList<T> mInputPtrs, mOutputPtrs;
222 double mInRatio = 0.0, mOutRatio = 0.0;
223 double mOuterSampleRate = DEFAULT_SAMPLE_RATE;
224 const double mInnerSampleRate;
225 int mMaxOuterLength = 0; // The maximum outer block size expected
226 int mMaxInnerLength = 0; // The computed maximum inner block size
227 int mLatency = 0;
228 const ESRCMode mResamplingMode;
229 std::unique_ptr<LanczosResampler> mInResampler, mOutResampler;
230} WDL_FIXALIGN;
231
232END_IPLUG_NAMESPACE
Include to get consistently named preprocessor macros for different platforms and logging functionali...
A multi-channel real-time resampling container that can be used to resample audio processing to a spe...
int GetLatency() const
Get the latency of the resampling, not including any latency of the encapsulated DSP.
void ProcessBlock(T **inputs, T **outputs, int nFrames, int nChans, BlockProcessFunc func)
Resample an input block with a per-block function (resample input -> process with function -> resampl...
RealtimeResampler(double innerSampleRate, ESRCMode mode=ESRCMode::kLancsoz)
Constructor.
void Reset(double inputSampleRate, int maxBlockSize=DEFAULT_BLOCK_SIZE)
Reset the underlying DSP (when the samplerate or max expected block size changes)