Skip to content

Commit

Permalink
Add multi-mode type to SVF (#251)
Browse files Browse the repository at this point in the history
* Implement multi-mode filter

* Clean up multi-mode filter and add tests

* Apply clang-format

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Jul 14, 2022
1 parent 59e83dd commit 9b0ffe3
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class NthOrderFilter

static_assert (type == StateVariableFilterType::Lowpass
|| type == StateVariableFilterType::Highpass
|| type == StateVariableFilterType::Bandpass,
|| type == StateVariableFilterType::Bandpass
|| type == StateVariableFilterType::MultiMode,
"NthOrderFilter is not defined for this filter type!");

NthOrderFilter() : butterQVals (QValCalcs::butterworth_Qs<NumericType, order>())
Expand Down Expand Up @@ -53,6 +54,18 @@ class NthOrderFilter
filters[0].setQValue (butterQVals[0] * qVal * juce::MathConstants<NumericType>::sqrt2);
}

/**
* Sets the Mode of a multi-mode filter. The mode parameter is expected to be in [0, 1],
* where 0 corresponds to a LPF, 0.5 corresponds to a BPF, and 1 corresponds to a HPF.
*/
template <StateVariableFilterType M = type>
std::enable_if_t<M == StateVariableFilterType::MultiMode, void>
setMode (NumericType mode)
{
for (auto& filt : filters)
filt.setMode (mode);
}

/** Processes a block of samples */
void processBlock (const chowdsp::BufferView<T>& buffer)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ void StateVariableFilter<SampleType, type>::setGainDecibels (SampleType newGainD
setGain<shouldUpdate> (SIMDUtils::decibelsToGain (newGainDecibels));
}

template <typename SampleType, StateVariableFilterType type>
template <StateVariableFilterType M>
std::enable_if_t<M == StateVariableFilterType::MultiMode, void>
StateVariableFilter<SampleType, type>::setMode (NumericType mode)
{
lowpassMult = (NumericType) 1 - (NumericType) 2 * juce::jmin ((NumericType) 0.5, mode);
bandpassMult = (NumericType) 1 - std::abs ((NumericType) 2 * (mode - (NumericType) 0.5)); // * juce::MathConstants<NumericType>::sqrt2;
highpassMult = (NumericType) 2 * juce::jmax ((NumericType) 0.5, mode) - (NumericType) 1;

// use sin3db power law for mixing
lowpassMult = std::sin (juce::MathConstants<NumericType>::halfPi * lowpassMult);
bandpassMult = std::sin (juce::MathConstants<NumericType>::halfPi * bandpassMult);
highpassMult = std::sin (juce::MathConstants<NumericType>::halfPi * highpassMult);

// the BPF is a little bit quieter by design, so let's compensate here for a smooth transition
bandpassMult *= juce::MathConstants<NumericType>::sqrt2;
}

template <typename SampleType, StateVariableFilterType type>
void StateVariableFilter<SampleType, type>::prepare (const juce::dsp::ProcessSpec& spec)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum class StateVariableFilterType
Bell,
LowShelf,
HighShelf,
MultiMode, /**< Allows the filter to be interpolated between lowpass, bandpass, and highpass */
};

/**
Expand Down Expand Up @@ -69,6 +70,13 @@ class StateVariableFilter
template <bool shouldUpdate = true>
void setGainDecibels (SampleType newGainDecibels);

/**
* Sets the Mode of a multi-mode filter. The mode parameter is expected to be in [0, 1],
* where 0 corresponds to a LPF, 0.5 corresponds to a BPF, and 1 corresponds to a HPF.
*/
template <FilterType M = type>
std::enable_if_t<M == FilterType::MultiMode, void> setMode (NumericType mode);

/**
* Updates the filter coefficients.
*
Expand Down Expand Up @@ -190,6 +198,8 @@ class StateVariableFilter
return Asq * v2 + k0A * v1 + v0; // Asq * low + k0 * A * band + high
else if constexpr (type == FilterType::HighShelf)
return Asq * v0 + k0A * v1 + v2; // Asq * high + k0 * A * band + low
else if constexpr (type == FilterType::MultiMode)
return lowpassMult * v2 + bandpassMult * v1 + highpassMult * v0;
else
{
jassertfalse; // unknown filter type!
Expand All @@ -202,6 +212,8 @@ class StateVariableFilter
SampleType a1, a2, a3, ak, k0A, Asq; // coefficients
std::vector<SampleType> ic1eq { 2 }, ic2eq { 2 }; // state variables

NumericType lowpassMult { 0 }, bandpassMult { 0 }, highpassMult { 0 };

double sampleRate = 44100.0;
};

Expand Down Expand Up @@ -236,6 +248,10 @@ using SVFLowShelf = StateVariableFilter<SampleType, StateVariableFilterType::Low
/** Convenient alias for an SVF high-shelf filter. */
template <typename SampleType = float>
using SVFHighShelf = StateVariableFilter<SampleType, StateVariableFilterType::HighShelf>;

/** Convenient alias for an SVF multi-mode filter. */
template <typename SampleType = float>
using SVFMultiMode = StateVariableFilter<SampleType, StateVariableFilterType::MultiMode>;
} // namespace chowdsp

#include "chowdsp_StateVariableFilter.cpp"
42 changes: 42 additions & 0 deletions tests/dsp_tests/chowdsp_filters_test/NthOrderFilterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void testFilter (Filter& filt, std::vector<float> freqs, std::vector<float> mags
TEMPLATE_TEST_CASE ("Nth Order Filter Test", "", float, xsimd::batch<float>)
{
using T = TestType;
using NumericType = chowdsp::SampleTypeHelpers::NumericType<T>;
using FilterType = chowdsp::StateVariableFilterType;

SECTION ("LPF Test")
Expand Down Expand Up @@ -71,4 +72,45 @@ TEMPLATE_TEST_CASE ("Nth Order Filter Test", "", float, xsimd::batch<float>)
// { 0.0001f, 0.01f, 0.01f },
// { "passband", "high cutoff", "low cutoff" });
}

SECTION ("Multi-Mode Test")
{
static constexpr int Order = 6;
static constexpr NumericType maxErr = 1.0e-1f;

std::random_device rd;
std::mt19937 mt (rd());
std::uniform_real_distribution<NumericType> minus1To1 ((NumericType) -1, (NumericType) 1);

chowdsp::NthOrderFilter<T, Order, chowdsp::StateVariableFilterType::MultiMode> testFilter;
testFilter.prepare ({ Constants::fs, (uint32_t) 128, 1 });

auto testCompare = [&] (auto& compareFilter, NumericType mode, NumericType freq, NumericType Q, NumericType gainComp = 1.0f) {
testFilter.reset();
testFilter.setCutoffFrequency (freq);
testFilter.setQValue (Q);
testFilter.setMode (mode);

compareFilter.prepare ({ Constants::fs, (uint32_t) 128, 1 });
compareFilter.setCutoffFrequency (freq);
compareFilter.setQValue (Q);

for (int i = 0; i < 1000; ++i)
{
const auto x = (T) minus1To1 (mt);
const auto actualY = testFilter.processSample (0, x);
const auto expY = compareFilter.processSample (0, x) * gainComp;
REQUIRE (actualY == SIMDApprox<T> (expY).margin (maxErr));
}
};

chowdsp::NthOrderFilter<T, Order, chowdsp::StateVariableFilterType::Lowpass> lpf;
chowdsp::NthOrderFilter<T, Order, chowdsp::StateVariableFilterType::Bandpass> bpf;
chowdsp::NthOrderFilter<T, Order, chowdsp::StateVariableFilterType::Highpass> hpf;
const auto bpfGainComp = std::pow (juce::MathConstants<NumericType>::sqrt2, NumericType (Order) / 2);

testCompare (lpf, (NumericType) 0.0, (NumericType) 1000.0, (NumericType) 2.0);
testCompare (bpf, (NumericType) 0.5, (NumericType) 500.0, (NumericType) 0.5, bpfGainComp);
testCompare (hpf, (NumericType) 1.0, (NumericType) 750.0, (NumericType) 1.0);
}
}

0 comments on commit 9b0ffe3

Please sign in to comment.