Skip to content

Commit

Permalink
Setting up example plugin for polyphase interpolation/decimation (#475)
Browse files Browse the repository at this point in the history
* Setting up example plugin for polyphase interpolation/decimation

* Trying to fix MSVC warning

* More MSVC warnings
  • Loading branch information
jatinchowdhury18 authored Dec 14, 2023
1 parent 3d9641b commit 8d4c752
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 7 deletions.
2 changes: 2 additions & 0 deletions examples/ForwardingTestPlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ target_link_libraries(ForwardingTestPlugin
juce::juce_dsp
chowdsp::chowdsp_reverb
chowdsp::chowdsp_sources
chowdsp::chowdsp_dsp_utils
chowdsp::chowdsp_plugin_state
)

Expand All @@ -21,6 +22,7 @@ target_sources(ForwardingTestPlugin
InternalPlugins/PolygonalOscPlugin.cpp
InternalPlugins/BandSplitPlugin.cpp
InternalPlugins/PlateReverb.cpp
InternalPlugins/PolyphaseOversamplingPlugin.cpp
)

target_compile_definitions(ForwardingTestPlugin
Expand Down
6 changes: 5 additions & 1 deletion examples/ForwardingTestPlugin/ForwardingTestPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ void ForwardingTestPlugin::prepareToPlay (double sampleRate, int samplesPerBlock
polygonalOsc.prepareToPlay (sampleRate, samplesPerBlock);
bandSplit.prepareToPlay (sampleRate, samplesPerBlock);
plateReverb.prepareToPlay (sampleRate, samplesPerBlock);
polyphaseOversampling.prepareToPlay (sampleRate, samplesPerBlock);
}

void ForwardingTestPlugin::processAudioBlock (juce::AudioBuffer<float>& buffer)
{
juce::MidiBuffer midi;
juce::MidiBuffer midi {};
if (auto* processor = getProcessorForIndex (state.params.processorChoice->getIndex()))
{
processor->processBlock (buffer, midi);
Expand Down Expand Up @@ -77,6 +78,9 @@ juce::AudioProcessor* ForwardingTestPlugin::getProcessorForIndex (int index)
if (index == 7)
return &plateReverb;

if (index == 8)
return &polyphaseOversampling;

return nullptr;
}

Expand Down
3 changes: 3 additions & 0 deletions examples/ForwardingTestPlugin/ForwardingTestPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "InternalPlugins/PolygonalOscPlugin.h"
#include "InternalPlugins/BandSplitPlugin.h"
#include "InternalPlugins/PlateReverb.h"
#include "InternalPlugins/PolyphaseOversamplingPlugin.h"

struct PluginParams : chowdsp::ParamHolder
{
Expand All @@ -29,6 +30,7 @@ struct PluginParams : chowdsp::ParamHolder
"Polygonal Oscillator",
"Band Split",
"Plate Reverb",
"Polyphase Oversampling",
},
0
};
Expand Down Expand Up @@ -56,6 +58,7 @@ class ForwardingTestPlugin : public chowdsp::PluginBase<chowdsp::PluginStateImpl
PolygonalOscPlugin polygonalOsc;
BandSplitPlugin bandSplit;
PlateReverb plateReverb;
PolyphaseOversamplingPlugin polyphaseOversampling;

struct ParamForwardingProvider
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "PolyphaseOversamplingPlugin.h"

namespace
{
constexpr int os_ratio = 4;

// Coefficients generated from: https://fiiir.com/
// Cutoff frequency: 0.13
// Transition bandwidth: 0.08
// Blackman window
constexpr std::array<float, 59> aa_coeffs {
0.000000000000000000f,
-0.000009290744190889f,
-0.000003179945796776f,
0.000082640816061341f,
0.000229223614031907f,
0.000263693640932877f,
-0.000037675811380258f,
-0.000683760580094780f,
-0.001253639060684718f,
-0.001027123632937930f,
0.000442319424678493f,
0.002640563958995431f,
0.003947942580809822f,
0.002523482958339593f,
-0.002055129745209706f,
-0.007561189288896839f,
-0.009677304885113274f,
-0.004738033395627680f,
0.006739390459359920f,
0.018445648506312032f,
0.020795751620257420f,
0.007230813110385698f,
-0.019183700519560042f,
-0.043745289771951856f,
-0.045627091908622811f,
-0.009231438741921514f,
0.064759296816463446f,
0.155809509825300757f,
0.230923660241676043f,
0.259999820916768321f,
0.230923660241676099f,
0.155809509825300757f,
0.064759296816463446f,
-0.009231438741921514f,
-0.045627091908622811f,
-0.043745289771951870f,
-0.019183700519560049f,
0.007230813110385697f,
0.020795751620257423f,
0.018445648506312043f,
0.006739390459359923f,
-0.004738033395627683f,
-0.009677304885113274f,
-0.007561189288896845f,
-0.002055129745209709f,
0.002523482958339594f,
0.003947942580809828f,
0.002640563958995434f,
0.000442319424678492f,
-0.001027123632937931f,
-0.001253639060684717f,
-0.000683760580094779f,
-0.000037675811380258f,
0.000263693640932877f,
0.000229223614031908f,
0.000082640816061341f,
-0.000003179945796776f,
-0.000009290744190889f,
0.000000000000000000f,
};
} // namespace

void PolyphaseOversamplingPlugin::prepareToPlay (double sample_rate, int samples_per_block)
{
const auto num_channels = getMainBusNumInputChannels();

os_buffer.setMaxSize (num_channels, samples_per_block * os_ratio);
gain.prepare ({
sample_rate * static_cast<double> (os_ratio),
static_cast<uint32_t> (samples_per_block * os_ratio),
static_cast<uint32_t> (num_channels),
});

std::array<float, aa_coeffs.size()> upsample_coeffs {};
std::copy (aa_coeffs.begin(), aa_coeffs.end(), upsample_coeffs.begin());
for (auto& coeff : upsample_coeffs)
coeff *= (float) os_ratio;

upsampler.prepare (os_ratio, num_channels, samples_per_block, upsample_coeffs);
downsampler.prepare (os_ratio, num_channels, samples_per_block * os_ratio, aa_coeffs);
}

void PolyphaseOversamplingPlugin::processAudioBlock (juce::AudioBuffer<float>& buffer)
{
const auto num_channels = buffer.getNumChannels();
const auto num_samples = buffer.getNumSamples();
os_buffer.setCurrentSize (num_channels, num_samples * os_ratio);

upsampler.processBlock (buffer, os_buffer);

gain.setGainDecibels (state.params.gain->getCurrentValue());
gain.process (os_buffer);

chowdsp::BufferMath::applyFunctionSIMD (os_buffer,
[] (auto x)
{
CHOWDSP_USING_XSIMD_STD (tanh);
return tanh (x);
});

downsampler.processBlock (os_buffer, buffer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <chowdsp_dsp_utils/chowdsp_dsp_utils.h>
#include <chowdsp_filters/chowdsp_filters.h>
#include <chowdsp_plugin_base/chowdsp_plugin_base.h>

struct PolyphaseOversampleParams : chowdsp::ParamHolder
{
PolyphaseOversampleParams()
{
add (gain);
}

chowdsp::GainDBParameter::Ptr gain {
PID { "gain", 100 },
"Gain",
juce::NormalisableRange { 0.0f, 30.0f },
0.0f,
};
};

class PolyphaseOversamplingPlugin : public chowdsp::PluginBase<chowdsp::PluginStateImpl<PolyphaseOversampleParams>>
{
public:
PolyphaseOversamplingPlugin() = default;

void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override {}
void processAudioBlock (juce::AudioBuffer<float>& buffer) override;

bool hasEditor() const override { return false; }
juce::AudioProcessorEditor* createEditor() override { return nullptr; }

private:
chowdsp::FIRPolyphaseInterpolator<float> upsampler;
chowdsp::FIRPolyphaseDecimator<float> downsampler;

chowdsp::Buffer<float> os_buffer {};
chowdsp::Gain<float> gain {};

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PolyphaseOversamplingPlugin)
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class FIRPolyphaseDecimator
{
buffers.emplace_back (numChannels, maxBlockSizeIn / decimationFactor);

auto& filter = filters.emplace_back (coeffsPerFilter);
auto& filter = filters.emplace_back ((int) coeffsPerFilter);
filter.prepare (numChannels);

std::fill (oneFilterCoeffs.begin(), oneFilterCoeffs.end(), T {});
Expand Down Expand Up @@ -99,7 +99,7 @@ class FIRPolyphaseDecimator
{
jassert (bufferIn.getNumChannels() == bufferOut.getNumChannels());
const auto numSamples = bufferIn.getNumSamples();
jassert (numSamples == bufferOut.getNumSamples() / (int) filters.size());
jassert (numSamples == bufferOut.getNumSamples() * (int) filters.size());

for (auto [ch, dataIn] : buffer_iters::channels (bufferIn))
processBlock (dataIn.data(), bufferOut.getWritePointer (ch), numSamples, ch);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class FIRPolyphaseInterpolator
{
buffers.emplace_back (numChannels, maxBlockSizeIn);

auto& filter = filters.emplace_back (coeffsPerFilter);
auto& filter = filters.emplace_back ((int) coeffsPerFilter);
filter.prepare (numChannels);

std::fill (oneFilterCoeffs.begin(), oneFilterCoeffs.end(), T {});
Expand Down Expand Up @@ -82,7 +82,7 @@ class FIRPolyphaseInterpolator
{
jassert (bufferIn.getNumChannels() == bufferOut.getNumChannels());
const auto numSamples = bufferIn.getNumSamples();
jassert (numSamples == bufferOut.getNumSamples() * (int) filters.size());
jassert (numSamples == bufferOut.getNumSamples() / (int) filters.size());

for (auto [ch, dataIn] : buffer_iters::channels (bufferIn))
processBlock (dataIn.data(), bufferOut.getWritePointer (ch), numSamples, ch);
Expand Down
5 changes: 3 additions & 2 deletions modules/dsp/chowdsp_math/Math/chowdsp_BufferMath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,16 @@ std::enable_if_t<std::is_floating_point_v<FloatType>, void>
#endif

static constexpr auto vecSize = (int) xsimd::batch<FloatType>::size;
auto numVecOps = numSamples / vecSize;
const auto numVecOps = numSamples / vecSize;
const auto leftoverValues = numSamples % vecSize;

for (int ch = 0; ch < numChannels; ++ch)
{
const auto* dataIn = bufferSrc.getReadPointer (ch);
auto* dataOut = bufferDest.getWritePointer (ch);

while (--numVecOps >= 0)
auto channelVecOps = numVecOps;
while (--channelVecOps >= 0)
{
xsimd::store_aligned (dataOut, simdFunction (xsimd::load_aligned (dataIn)));
dataIn += vecSize;
Expand Down

0 comments on commit 8d4c752

Please sign in to comment.