Files
tlib/oversampling/Oversampler.h
2024-06-04 14:31:43 +02:00

460 lines
14 KiB
C++

/*
==============================================================================
This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
Modified by Christopher Herb for standalone use.
See LICENSE.txt for more info.
==============================================================================
*/
#pragma once
#define OVERSAMPLING_FACTORS_VA_LIST "None", "2x", "4x", "8x", "16x"
#include <assert.h>
#include <cmath>
#include <functional>
#include "HIIR/FPUDownsampler2x.h"
#include "HIIR/FPUUpsampler2x.h"
#include "WDL/heapbuf.h"
#include "WDL/ptrlist.h"
// #include "IPlugPlatform.h"
using namespace hiir;
enum EFactor {
kNone = 0,
k2x,
k4x,
k8x,
k16x,
kNumFactors
};
template <typename T = double>
class OverSampler {
public:
using BlockProcessFunc = std::function<void(T**, T**, int)>;
OverSampler(EFactor factor = kNone, bool blockProcessing = true, int nInChannels = 1, int nOutChannels = 1)
: mBlockProcessing(blockProcessing)
, mNInChannels(nInChannels)
, mNOutChannels(nOutChannels)
{
static constexpr double coeffs2x[12] = {0.036681502163648017, 0.13654762463195794, 0.27463175937945444,
0.42313861743656711, 0.56109869787919531, 0.67754004997416184,
0.76974183386322703, 0.83988962484963892, 0.89226081800387902,
0.9315419599631839, 0.96209454837808417, 0.98781637073289585};
static constexpr double coeffs4x[4] = {0.041893991997656171, 0.16890348243995201, 0.39056077292116603,
0.74389574826847926};
static constexpr double coeffs8x[3] = {0.055748680811302048, 0.24305119574153072, 0.64669913119268196};
static constexpr double coeffs16x[2] = {0.10717745346023573, 0.53091435354504557};
for (auto c = 0; c < mNInChannels; c++) {
mUpsampler2x.Add(new Upsampler2xFPU<12, T>());
mUpsampler4x.Add(new Upsampler2xFPU<4, T>());
mUpsampler8x.Add(new Upsampler2xFPU<3, T>());
mUpsampler16x.Add(new Upsampler2xFPU<2, T>());
mUpsampler2x.Get(c)->set_coefs(coeffs2x);
mUpsampler4x.Get(c)->set_coefs(coeffs4x);
mUpsampler8x.Get(c)->set_coefs(coeffs8x);
mUpsampler16x.Get(c)->set_coefs(coeffs16x);
// ptr location doesn't matter at this stage
mNextInputPtrs.Add(mUp2x.Get());
}
for (auto c = 0; c < mNOutChannels; c++) {
mDownsampler2x.Add(new Downsampler2xFPU<12, T>());
mDownsampler4x.Add(new Downsampler2xFPU<4, T>());
mDownsampler8x.Add(new Downsampler2xFPU<3, T>());
mDownsampler16x.Add(new Downsampler2xFPU<2, T>());
mDownsampler2x.Get(c)->set_coefs(coeffs2x);
mDownsampler4x.Get(c)->set_coefs(coeffs4x);
mDownsampler8x.Get(c)->set_coefs(coeffs8x);
mDownsampler16x.Get(c)->set_coefs(coeffs16x);
// ptr location doesn't matter at this stage
mNextOutputPtrs.Add(mDown2x.Get());
}
SetOverSampling(factor);
Reset();
}
~OverSampler()
{
mUpsampler2x.Empty(true);
mDownsampler2x.Empty(true);
mUpsampler4x.Empty(true);
mDownsampler4x.Empty(true);
mUpsampler8x.Empty(true);
mDownsampler8x.Empty(true);
mUpsampler16x.Empty(true);
mDownsampler16x.Empty(true);
}
OverSampler(const OverSampler&) = delete;
OverSampler& operator=(const OverSampler&) = delete;
void Reset(int blockSize = 16)
{
int numBufSamples = 1;
if (mBlockProcessing) numBufSamples = blockSize;
else {
numBufSamples = 1;
blockSize = 1;
}
numBufSamples *= mNInChannels;
mUp2x.Resize(2 * numBufSamples);
mUp4x.Resize(4 * numBufSamples);
mUp8x.Resize(8 * numBufSamples);
mUp16x.Resize(16 * numBufSamples);
mDown2x.Resize(2 * numBufSamples);
mDown4x.Resize(4 * numBufSamples);
mDown8x.Resize(8 * numBufSamples);
mDown16x.Resize(16 * numBufSamples);
mUp16BufferPtrs.Empty();
mUp8BufferPtrs.Empty();
mUp4BufferPtrs.Empty();
mUp2BufferPtrs.Empty();
mDown16BufferPtrs.Empty();
mDown8BufferPtrs.Empty();
mDown4BufferPtrs.Empty();
mDown2BufferPtrs.Empty();
for (auto c = 0; c < mNInChannels; c++) {
mUpsampler2x.Get(c)->clear_buffers();
mUpsampler4x.Get(c)->clear_buffers();
mUpsampler8x.Get(c)->clear_buffers();
mUpsampler16x.Get(c)->clear_buffers();
mUp2BufferPtrs.Add(mUp2x.Get() + c * 2 * blockSize);
mUp4BufferPtrs.Add(mUp4x.Get() + (c * 4 * blockSize));
mUp8BufferPtrs.Add(mUp8x.Get() + (c * 8 * blockSize));
mUp16BufferPtrs.Add(mUp16x.Get() + (c * 16 * blockSize));
}
for (auto c = 0; c < mNOutChannels; c++) {
mDownsampler2x.Get(c)->clear_buffers();
mDownsampler4x.Get(c)->clear_buffers();
mDownsampler8x.Get(c)->clear_buffers();
mDownsampler16x.Get(c)->clear_buffers();
mDown2BufferPtrs.Add(mDown2x.Get() + c * 2 * blockSize);
mDown4BufferPtrs.Add(mDown4x.Get() + (c * 4 * blockSize));
mDown8BufferPtrs.Add(mDown8x.Get() + (c * 8 * blockSize));
mDown16BufferPtrs.Add(mDown16x.Get() + (c * 16 * blockSize));
}
}
/** Over sample an input block with a per-block function (up sample input -> process with function -> down sample)
* @param inputs Two-dimensional array containing the non-interleaved input buffers of audio samples for all
* channels
* @param outputs Two-dimensional array for audio output (non-interleaved).
* @param nFrames The block size for this block: number of samples per channel.
* @param nInChans The number of input channels to process. Must be less or equal to the number of channels passed
* to the constructor
* @param nOutChans The number of output channels to process. Must be less or equal to the number of channels passed
* to the constructor
* @param func The function that processes the audio sample at the higher sampling rate. NOTE: std::function can
* call malloc if you pass in captures */
void ProcessBlock(T** inputs, T** outputs, int nFrames, int nInChans, int nOutChans, BlockProcessFunc func)
{
assert(nInChans <= mNInChannels);
assert(nOutChans <= mNOutChannels);
if (mRate != mPrevRate) {
switch (mRate) {
case 2:
mInPtrLoopSrc = &mUp2BufferPtrs;
mOutPtrLoopSrc = &mDown2BufferPtrs;
break;
case 4:
mInPtrLoopSrc = &mUp4BufferPtrs;
mOutPtrLoopSrc = &mDown4BufferPtrs;
break;
case 8:
mInPtrLoopSrc = &mUp8BufferPtrs;
mOutPtrLoopSrc = &mDown8BufferPtrs;
break;
case 16:
mInPtrLoopSrc = &mUp16BufferPtrs;
mOutPtrLoopSrc = &mDown16BufferPtrs;
break;
default:
break;
}
mPrevRate = mRate;
}
for (auto c = 0; c < nInChans; c++) {
if (mRate >= 2) { mUpsampler2x.Get(c)->process_block(mUp2BufferPtrs.Get(c), inputs[c], nFrames); }
if (mRate >= 4) {
mUpsampler4x.Get(c)->process_block(mUp4BufferPtrs.Get(c), mUp2BufferPtrs.Get(c), nFrames * 2);
}
if (mRate >= 8) {
mUpsampler8x.Get(c)->process_block(mUp8BufferPtrs.Get(c), mUp4BufferPtrs.Get(c), nFrames * 4);
}
if (mRate == 16) {
mUpsampler16x.Get(c)->process_block(mUp16BufferPtrs.Get(c), mUp8BufferPtrs.Get(c), nFrames * 8);
}
}
if (mRate == 1) {
func(inputs, outputs, nFrames);
} else {
for (auto i = 0; i < mRate; i++) {
for (auto c = 0; c < nInChans; c++) {
mNextInputPtrs.Set(c, mInPtrLoopSrc->Get(c) + (i * nFrames));
mNextOutputPtrs.Set(c, mOutPtrLoopSrc->Get(c) + (i * nFrames));
}
func(mNextInputPtrs.GetList(), mNextOutputPtrs.GetList(), nFrames);
}
}
for (auto c = 0; c < nOutChans; c++) {
if (mRate == 16) {
mDownsampler16x.Get(c)->process_block(mDown8BufferPtrs.Get(c), mDown16BufferPtrs.Get(c), nFrames * 8);
}
if (mRate >= 8) {
mDownsampler8x.Get(c)->process_block(mDown4BufferPtrs.Get(c), mDown8BufferPtrs.Get(c), nFrames * 4);
}
if (mRate >= 4) {
mDownsampler4x.Get(c)->process_block(mDown2BufferPtrs.Get(c), mDown4BufferPtrs.Get(c), nFrames * 2);
}
if (mRate >= 2) { mDownsampler2x.Get(c)->process_block(outputs[c], mDown2BufferPtrs.Get(c), nFrames); }
}
}
/** Over sample an input sample with a per-sample function (up-sample input -> process with function -> down-sample)
* @param input The audio sample to input
* @param std::function<double(double)> The function that processes the audio sample at the higher sampling rate.
* NOTE: std::function can call malloc if you pass in captures
* @return The audio sample output */
T Process(T input, std::function<T(T)> func)
{
T output;
if (mRate == 16) {
mUpsampler2x.Get(0)->process_sample(mUp2x.Get()[0], mUp2x.Get()[1], input);
mUpsampler4x.Get(0)->process_block(mUp4x.Get(), mUp2x.Get(), 2);
mUpsampler8x.Get(0)->process_block(mUp8x.Get(), mUp4x.Get(), 4);
mUpsampler16x.Get(0)->process_block(mUp16x.Get(), mUp8x.Get(), 8);
for (auto i = 0; i < 16; i++) { mDown16x.Get()[i] = func(mUp16x.Get()[i]); }
mDownsampler16x.Get(0)->process_block(mDown8x.Get(), mDown16x.Get(), 8);
mDownsampler8x.Get(0)->process_block(mDown4x.Get(), mDown8x.Get(), 4);
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
output = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
} else if (mRate == 8) {
mUpsampler2x.Get(0)->process_sample(mUp2x.Get()[0], mUp2x.Get()[1], input);
mUpsampler4x.Get(0)->process_block(mUp4x.Get(), mUp2x.Get(), 2);
mUpsampler8x.Get(0)->process_block(mUp8x.Get(), mUp4x.Get(), 4);
for (auto i = 0; i < 8; i++) { mDown8x.Get()[i] = func(mUp8x.Get()[i]); }
mDownsampler8x.Get(0)->process_block(mDown4x.Get(), mDown8x.Get(), 4);
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
output = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
} else if (mRate == 4) {
mUpsampler2x.Get(0)->process_sample(mUp2x.Get()[0], mUp2x.Get()[1], input);
mUpsampler4x.Get(0)->process_block(mUp4x.Get(), mUp2x.Get(), 2);
for (auto i = 0; i < 4; i++) { mDown4x.Get()[i] = func(mUp4x.Get()[i]); }
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
output = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
} else if (mRate == 2) {
mUpsampler2x.Get(0)->process_sample(mUp2x.Get()[0], mUp2x.Get()[1], input);
mDown2x.Get()[0] = func(mUp2x.Get()[0]);
mDown2x.Get()[1] = func(mUp2x.Get()[1]);
output = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
} else {
output = func(input);
}
return output;
}
/** Over-sample an per-sample synthesis function
* @param genFunc The function that generates the audio sample
* @return The audio sample output */
T ProcessGen(std::function<T()> genFunc)
{
auto ProcessDown16x = [&](T input) {
mDown16x.Get()[mWritePos] = (T)input;
mWritePos++;
mWritePos &= 15;
if (mWritePos == 0) {
mDownsampler16x.Get(0)->process_block(mDown8x.Get(), mDown16x.Get(), 8);
mDownsampler8x.Get(0)->process_block(mDown4x.Get(), mDown8x.Get(), 4);
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
mDownSamplerOutput = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
}
};
auto ProcessDown8x = [&](T input) {
mDown8x.Get()[mWritePos] = (T)input;
mWritePos++;
mWritePos &= 7;
if (mWritePos == 0) {
mDownsampler8x.Get(0)->process_block(mDown4x.Get(), mDown8x.Get(), 4);
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
mDownSamplerOutput = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
}
};
auto ProcessDown4x = [&](T input) {
mDown4x.Get()[mWritePos] = (T)input;
mWritePos++;
mWritePos &= 3;
if (mWritePos == 0) {
mDownsampler4x.Get(0)->process_block(mDown2x.Get(), mDown4x.Get(), 2);
mDownSamplerOutput = mDownsampler2x.Get(0)->process_sample(mDown2x.Get());
}
};
auto ProcessDown2x = [&](T input) {
mDown2x.Get()[mWritePos] = (T)input;
mWritePos = !mWritePos;
if (mWritePos == 0) { mDownSamplerOutput = mDownsampler2x.Get(0)->process_sample(mDown2x.Get()); }
};
T output;
for (int j = 0; j < mRate; j++) {
output = genFunc();
switch (mRate) {
case 2:
ProcessDown2x(output);
break;
case 4:
ProcessDown4x(output);
break;
case 8:
ProcessDown8x(output);
break;
case 16:
ProcessDown16x(output);
break;
default:
break;
}
}
if (mRate > 1) output = mDownSamplerOutput;
return output;
}
void SetOverSampling(EFactor factor)
{
if (factor != mFactor) {
mFactor = factor;
mRate = std::pow(2, (int)factor);
Reset();
}
}
static EFactor RateToFactor(int rate)
{
switch (rate) {
case 1:
return EFactor::kNone;
case 2:
return EFactor::k2x;
case 4:
return EFactor::k4x;
case 8:
return EFactor::k8x;
case 16:
return EFactor::k16x;
default:
assert(0);
return EFactor::kNone;
}
}
int GetRate() const { return mRate; }
private:
EFactor mFactor = kNone;
int mPrevRate = 0;
int mRate = 1;
int mWritePos = 0;
T mDownSamplerOutput = 0.;
bool mBlockProcessing; // false
int mNInChannels; // 1
int mNOutChannels;
// the actual data
WDL_TypedBuf<T> mUp16x;
WDL_TypedBuf<T> mUp8x;
WDL_TypedBuf<T> mUp4x;
WDL_TypedBuf<T> mUp2x;
WDL_TypedBuf<T> mDown16x;
WDL_TypedBuf<T> mDown8x;
WDL_TypedBuf<T> mDown4x;
WDL_TypedBuf<T> mDown2x;
// Ptrs into buffer data
WDL_PtrList<T> mUp16BufferPtrs;
WDL_PtrList<T> mUp8BufferPtrs;
WDL_PtrList<T> mUp4BufferPtrs;
WDL_PtrList<T> mUp2BufferPtrs;
WDL_PtrList<T> mDown16BufferPtrs;
WDL_PtrList<T> mDown8BufferPtrs;
WDL_PtrList<T> mDown4BufferPtrs;
WDL_PtrList<T> mDown2BufferPtrs;
WDL_PtrList<T> mNextInputPtrs;
WDL_PtrList<T> mNextOutputPtrs;
// Ptrs to the buffer data ptrs, changed depending on rate (block processing only)
WDL_PtrList<T>* mInPtrLoopSrc = nullptr;
WDL_PtrList<T>* mOutPtrLoopSrc = nullptr;
// Ptrs to oversamplers for each channel
WDL_PtrList<Upsampler2xFPU<12, T>> mUpsampler2x; // for 1x to 2x SR
WDL_PtrList<Upsampler2xFPU<4, T>> mUpsampler4x; // for 2x to 4x SR
WDL_PtrList<Upsampler2xFPU<3, T>> mUpsampler8x; // for 4x to 8x SR
WDL_PtrList<Upsampler2xFPU<2, T>> mUpsampler16x; // for 8x to 16x SR
WDL_PtrList<Downsampler2xFPU<12, T>> mDownsampler2x; // decimator for 2x to 1x SR
WDL_PtrList<Downsampler2xFPU<4, T>> mDownsampler4x; // decimator for 4x to 2x SR
WDL_PtrList<Downsampler2xFPU<3, T>> mDownsampler8x; // decimator for 8x to 4x SR
WDL_PtrList<Downsampler2xFPU<2, T>> mDownsampler16x; // decimator for 16x to 8x SR
};