/* ============================================================================== 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 #include #include #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 class OverSampler { public: using BlockProcessFunc = std::function; 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 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 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 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 mUp16x; WDL_TypedBuf mUp8x; WDL_TypedBuf mUp4x; WDL_TypedBuf mUp2x; WDL_TypedBuf mDown16x; WDL_TypedBuf mDown8x; WDL_TypedBuf mDown4x; WDL_TypedBuf mDown2x; // Ptrs into buffer data WDL_PtrList mUp16BufferPtrs; WDL_PtrList mUp8BufferPtrs; WDL_PtrList mUp4BufferPtrs; WDL_PtrList mUp2BufferPtrs; WDL_PtrList mDown16BufferPtrs; WDL_PtrList mDown8BufferPtrs; WDL_PtrList mDown4BufferPtrs; WDL_PtrList mDown2BufferPtrs; WDL_PtrList mNextInputPtrs; WDL_PtrList mNextOutputPtrs; // Ptrs to the buffer data ptrs, changed depending on rate (block processing only) WDL_PtrList* mInPtrLoopSrc = nullptr; WDL_PtrList* mOutPtrLoopSrc = nullptr; // Ptrs to oversamplers for each channel WDL_PtrList> mUpsampler2x; // for 1x to 2x SR WDL_PtrList> mUpsampler4x; // for 2x to 4x SR WDL_PtrList> mUpsampler8x; // for 4x to 8x SR WDL_PtrList> mUpsampler16x; // for 8x to 16x SR WDL_PtrList> mDownsampler2x; // decimator for 2x to 1x SR WDL_PtrList> mDownsampler4x; // decimator for 4x to 2x SR WDL_PtrList> mDownsampler8x; // decimator for 8x to 4x SR WDL_PtrList> mDownsampler16x; // decimator for 16x to 8x SR };