-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
aa8ce9c
commit a41000a
Showing
6 changed files
with
202 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
modules/dsp/chowdsp_filters/Other/chowdsp_FIRPolyphaseInterpolator.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#pragma once | ||
|
||
namespace chowdsp | ||
{ | ||
/** | ||
* A polyphase FIR interpolation filter. | ||
* | ||
* Reference: http://www.ws.binghamton.edu/fowler/fowler%20personal%20page/EE521_files/IV-05%20Polyphase%20FIlters%20Revised.pdf | ||
*/ | ||
template <typename T> | ||
class FIRPolyphaseInterpolator | ||
{ | ||
public: | ||
FIRPolyphaseInterpolator() = default; | ||
|
||
/** Prepares the filter to process a stream of data, with the given configuration and filter coefficients. */ | ||
void prepare (int interpolationFactor, int numChannels, int maxBlockSizeIn, const nonstd::span<const T> coeffs) | ||
{ | ||
const auto numCoeffs = coeffs.size(); | ||
const auto coeffsPerFilter = Math::ceiling_divide (numCoeffs, (size_t) interpolationFactor); | ||
|
||
std::vector<float> oneFilterCoeffs (coeffsPerFilter); | ||
|
||
buffers.clear(); | ||
buffers.reserve ((size_t) interpolationFactor); | ||
filters.clear(); | ||
filters.reserve ((size_t) interpolationFactor); | ||
for (int i = 0; i < interpolationFactor; ++i) | ||
{ | ||
buffers.emplace_back (numChannels, maxBlockSizeIn); | ||
|
||
auto& filter = filters.emplace_back (coeffsPerFilter); | ||
filter.prepare (numChannels); | ||
|
||
std::fill (oneFilterCoeffs.begin(), oneFilterCoeffs.end(), T{}); | ||
for (size_t j = 0; j < coeffsPerFilter; ++j) | ||
{ | ||
const auto index = (size_t) i + j * (size_t) interpolationFactor; | ||
oneFilterCoeffs[j] = index >= coeffs.size() ? T{} : coeffs[index]; | ||
} | ||
filter.setCoefficients (oneFilterCoeffs.data()); | ||
} | ||
} | ||
|
||
/** | ||
* Processes a block of data. | ||
* | ||
* inBlock should have a size of numSamplesIn, and outBlock should have a size of | ||
* numSamplesIn * interpolationFactor. | ||
*/ | ||
void processBlock (const T* inBlock, T* outBlock, const int numSamplesIn, const int channel = 0) noexcept | ||
{ | ||
const auto interpolationFactor = (int) filters.size(); | ||
|
||
// set up sub-buffer pointers | ||
auto* bufferPtrs = static_cast<T**> (alloca (sizeof (T*) * (size_t) interpolationFactor)); | ||
for (size_t filterIndex = 0; filterIndex < (size_t) interpolationFactor; ++filterIndex) | ||
bufferPtrs[filterIndex] = buffers[filterIndex].getWritePointer (channel); | ||
|
||
// process sub-buffers | ||
for (size_t filterIndex = 0; filterIndex < (size_t) interpolationFactor; ++filterIndex) | ||
filters[filterIndex].processBlock (inBlock, bufferPtrs[filterIndex], numSamplesIn, channel); | ||
|
||
// fill output buffer | ||
for (size_t filterIndex = 0; filterIndex < (size_t) interpolationFactor; ++filterIndex) | ||
for (int n = 0; n < numSamplesIn; ++n) | ||
outBlock[n * interpolationFactor + (int) filterIndex] = bufferPtrs[filterIndex][n]; | ||
} | ||
|
||
/** | ||
* Processes a block of data. | ||
* | ||
* bufferOut should have a size of bufferIn.getNumSamples() * interpolationFactor. | ||
*/ | ||
void processBlock (const BufferView<const T>& bufferIn, const BufferView<T>& bufferOut) noexcept | ||
{ | ||
jassert (bufferIn.getNumChannels() == bufferOut.getNumChannels()); | ||
const auto numSamples = bufferIn.getNumSamples(); | ||
jassert (numSamples == bufferOut.getNumSamples() * (int) filters.size()); | ||
|
||
for (auto [ch, dataIn] : buffer_iters::channels (bufferIn)) | ||
processBlock (dataIn.data(), bufferOut.getWritePointer (ch), numSamples, ch); | ||
} | ||
|
||
private: | ||
std::vector<FIRFilter<T>> filters{}; | ||
|
||
std::vector<Buffer<T>> buffers{}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseInterpolatorTest.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#include <CatchUtils.h> | ||
#include <chowdsp_filters/chowdsp_filters.h> | ||
|
||
static void interpolationFilterCompare (int filterOrder, int interpolationFactor, int numChannels) | ||
{ | ||
const auto numSamples = 12; | ||
const auto halfSamples = numSamples / 2; | ||
|
||
chowdsp::Buffer<float> bufferIn{ numChannels, numSamples }; | ||
for (auto [ch, data] : chowdsp::buffer_iters::channels (bufferIn)) | ||
for (auto [n, x] : chowdsp::enumerate (data)) | ||
x = static_cast<float> (n + (size_t) ch); | ||
|
||
std::vector coeffs ((size_t) filterOrder, 0.0f); | ||
for (auto [k, h] : chowdsp::enumerate (coeffs)) | ||
h = static_cast<float> (k); | ||
|
||
chowdsp::FIRFilter<float> referenceFilter{ filterOrder }; | ||
referenceFilter.prepare (numChannels); | ||
referenceFilter.setCoefficients (coeffs.data()); | ||
|
||
chowdsp::Buffer<float> referenceBufferIn{ numChannels, numSamples * interpolationFactor }; | ||
chowdsp::Buffer<float> referenceBufferOut{ numChannels, numSamples * interpolationFactor }; | ||
referenceBufferIn.clear(); | ||
for (auto [ch, data] : chowdsp::buffer_iters::channels (std::as_const (bufferIn))) | ||
for (auto [n, x] : chowdsp::enumerate (data)) | ||
referenceBufferIn.getWritePointer (ch)[(int) n * interpolationFactor] = x; | ||
referenceFilter.processBlock (chowdsp::BufferView{ referenceBufferIn, 0, halfSamples * interpolationFactor }, | ||
chowdsp::BufferView{ referenceBufferOut, 0, halfSamples * interpolationFactor }); | ||
referenceFilter.processBlock (chowdsp::BufferView{ referenceBufferIn, halfSamples * interpolationFactor, halfSamples * interpolationFactor }, | ||
chowdsp::BufferView{ referenceBufferOut, halfSamples * interpolationFactor, halfSamples * interpolationFactor }); | ||
|
||
chowdsp::FIRPolyphaseInterpolator<float> interpolatorFilter; | ||
interpolatorFilter.prepare (interpolationFactor, numChannels, numSamples, coeffs); | ||
chowdsp::Buffer<float> testBufferOut{ numChannels, numSamples * interpolationFactor }; | ||
interpolatorFilter.processBlock (chowdsp::BufferView{ bufferIn, 0, halfSamples }, | ||
chowdsp::BufferView{ testBufferOut, 0, halfSamples * interpolationFactor }); | ||
interpolatorFilter.processBlock (chowdsp::BufferView{ bufferIn, halfSamples, halfSamples }, | ||
chowdsp::BufferView{ testBufferOut, halfSamples * interpolationFactor, halfSamples * interpolationFactor }); | ||
|
||
for (int ch = 0; ch < numChannels; ++ch) | ||
{ | ||
for (int n = 0; n < numSamples * interpolationFactor; ++n) | ||
{ | ||
const auto ref = referenceBufferOut.getReadPointer (ch)[n]; | ||
const auto test = testBufferOut.getReadPointer (ch)[n]; | ||
REQUIRE (test == Catch::Approx { ref }.margin (1.0e-6)); | ||
} | ||
} | ||
} | ||
|
||
TEST_CASE ("FIR Polyphase Interpolator Test", "[dsp][filters][fir][anti-aliasing]") | ||
{ | ||
interpolationFilterCompare (10, 2, 1); | ||
interpolationFilterCompare (9, 2, 1); | ||
|
||
interpolationFilterCompare (16, 3, 4); | ||
interpolationFilterCompare (19, 3, 4); | ||
|
||
interpolationFilterCompare (32, 4, 2); | ||
interpolationFilterCompare (33, 4, 2); | ||
} |