-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy over LadderFilter implementation
- Loading branch information
Showing
4 changed files
with
304 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
#include "LadderFilter.h" | ||
|
||
namespace blackbird::dsp { | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
LadderFilter<SampleType>::LadderFilter() : state(2) { | ||
setSampleRate( | ||
SampleType(1000)); // intentionally setting unrealistic default | ||
// sample rate to catch missing initialisation bugs | ||
setResonance(SampleType(0)); | ||
setDrive(SampleType(1.2)); | ||
|
||
mode = Mode::LPF24; | ||
setMode(Mode::LPF12); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::setMode(Mode newMode) noexcept { | ||
if (newMode == mode) | ||
return; | ||
|
||
switch (newMode) { | ||
case Mode::LPF12: | ||
A = {{SampleType(0), SampleType(0), SampleType(1), SampleType(0), | ||
SampleType(0)}}; | ||
comp = SampleType(0.5); | ||
break; | ||
case Mode::HPF12: | ||
A = {{SampleType(1), SampleType(-2), SampleType(1), SampleType(0), | ||
SampleType(0)}}; | ||
comp = SampleType(0); | ||
break; | ||
case Mode::BPF12: | ||
A = {{SampleType(0), SampleType(0), SampleType(-1), SampleType(1), | ||
SampleType(0)}}; | ||
comp = SampleType(0.5); | ||
break; | ||
case Mode::LPF24: | ||
A = {{SampleType(0), SampleType(0), SampleType(0), SampleType(0), | ||
SampleType(1)}}; | ||
comp = SampleType(0.5); | ||
break; | ||
case Mode::HPF24: | ||
A = {{SampleType(1), SampleType(-4), SampleType(6), SampleType(-4), | ||
SampleType(1)}}; | ||
comp = SampleType(0); | ||
break; | ||
case Mode::BPF24: | ||
A = {{SampleType(0), SampleType(0), SampleType(1), SampleType(-2), | ||
SampleType(1)}}; | ||
comp = SampleType(0.5); | ||
break; | ||
default: | ||
jassertfalse; | ||
break; | ||
} | ||
|
||
static constexpr auto outputGain = SampleType(1.2); | ||
|
||
for (auto &a : A) | ||
a *= outputGain; | ||
|
||
mode = newMode; | ||
reset(); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::prepare(const ProcessSpec &spec) { | ||
setSampleRate(SampleType(spec.sampleRate)); | ||
setNumChannels(spec.numChannels); | ||
reset(); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> void LadderFilter<SampleType>::reset() noexcept { | ||
for (auto &s : state) | ||
s.fill(SampleType(0)); | ||
|
||
cutoffTransformSmoother.setCurrentAndTargetValue( | ||
cutoffTransformSmoother.getTargetValue()); | ||
scaledResonanceSmoother.setCurrentAndTargetValue( | ||
scaledResonanceSmoother.getTargetValue()); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::setCutoffFrequencyHz( | ||
SampleType newCutoff) noexcept { | ||
jassert(newCutoff > SampleType(0)); | ||
cutoffFreqHz = newCutoff; | ||
updateCutoffFreq(); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::setResonance(SampleType newResonance) noexcept { | ||
jassert(newResonance >= SampleType(0) && newResonance <= SampleType(1)); | ||
resonance = newResonance; | ||
updateResonance(); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::setDrive(SampleType newDrive) noexcept { | ||
jassert(newDrive >= SampleType(1)); | ||
|
||
drive = newDrive; | ||
gain = std::pow(drive, SampleType(-2.642)) * SampleType(0.6103) + | ||
SampleType(0.3903); | ||
drive2 = drive * SampleType(0.04) + SampleType(0.96); | ||
gain2 = std::pow(drive2, SampleType(-2.642)) * SampleType(0.6103) + | ||
SampleType(0.3903); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
SampleType | ||
LadderFilter<SampleType>::processSample(SampleType inputValue, | ||
size_t channelToUse) noexcept { | ||
auto &s = state[channelToUse]; | ||
|
||
const auto a1 = cutoffTransformValue; | ||
const auto g = a1 * SampleType(-1) + SampleType(1); | ||
const auto b0 = g * SampleType(0.76923076923); | ||
const auto b1 = g * SampleType(0.23076923076); | ||
|
||
const auto dx = gain * saturationLUT(drive * inputValue); | ||
const auto a = dx + scaledResonanceValue * SampleType(-4) * | ||
(gain2 * saturationLUT(drive2 * s[4]) - dx * comp); | ||
|
||
const auto b = b1 * s[0] + a1 * s[1] + b0 * a; | ||
const auto c = b1 * s[1] + a1 * s[2] + b0 * b; | ||
const auto d = b1 * s[2] + a1 * s[3] + b0 * c; | ||
const auto e = b1 * s[3] + a1 * s[4] + b0 * d; | ||
|
||
s[0] = a; | ||
s[1] = b; | ||
s[2] = c; | ||
s[3] = d; | ||
s[4] = e; | ||
|
||
return a * A[0] + b * A[1] + c * A[2] + d * A[3] + e * A[4]; | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::updateSmoothers() noexcept { | ||
cutoffTransformValue = cutoffTransformSmoother.getNextValue(); | ||
scaledResonanceValue = scaledResonanceSmoother.getNextValue(); | ||
} | ||
|
||
//============================================================================== | ||
template <typename SampleType> | ||
void LadderFilter<SampleType>::setSampleRate(SampleType newValue) noexcept { | ||
jassert(newValue > SampleType(0)); | ||
cutoffFreqScaler = | ||
SampleType(-2.0 * juce::MathConstants<double>::pi) / newValue; | ||
|
||
static constexpr SampleType smootherRampTimeSec = SampleType(0.05); | ||
cutoffTransformSmoother.reset(newValue, smootherRampTimeSec); | ||
scaledResonanceSmoother.reset(newValue, smootherRampTimeSec); | ||
|
||
updateCutoffFreq(); | ||
} | ||
|
||
//============================================================================== | ||
template class LadderFilter<float>; | ||
template class LadderFilter<double>; | ||
|
||
} // namespace blackbird::dsp |
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,127 @@ | ||
#pragma once | ||
|
||
#include <juce_dsp/juce_dsp.h> | ||
|
||
namespace blackbird::dsp { | ||
|
||
using namespace juce; | ||
using namespace juce::dsp; | ||
|
||
/** | ||
Multi-mode filter based on the Moog ladder filter. | ||
@tags{DSP} | ||
*/ | ||
template <typename SampleType> class LadderFilter { | ||
public: | ||
//============================================================================== | ||
using Mode = LadderFilterMode; | ||
|
||
//============================================================================== | ||
/** Creates an uninitialised filter. Call prepare() before first use. */ | ||
LadderFilter(); | ||
|
||
/** Enables or disables the filter. If disabled it will simply pass through | ||
* the input signal. */ | ||
void setEnabled(bool isEnabled) noexcept { enabled = isEnabled; } | ||
|
||
/** Sets filter mode. */ | ||
void setMode(Mode newMode) noexcept; | ||
|
||
/** Initialises the filter. */ | ||
void prepare(const ProcessSpec &spec); | ||
|
||
/** Returns the current number of channels. */ | ||
size_t getNumChannels() const noexcept { return state.size(); } | ||
|
||
/** Resets the internal state variables of the filter. */ | ||
void reset() noexcept; | ||
|
||
/** Sets the cutoff frequency of the filter. | ||
@param newCutoff cutoff frequency in Hz | ||
*/ | ||
void setCutoffFrequencyHz(SampleType newCutoff) noexcept; | ||
|
||
/** Sets the resonance of the filter. | ||
@param newResonance a value between 0 and 1; higher values increase the | ||
resonance and can result in self oscillation! | ||
*/ | ||
void setResonance(SampleType newResonance) noexcept; | ||
|
||
/** Sets the amount of saturation in the filter. | ||
@param newDrive saturation amount; it can be any number greater than or | ||
equal to one. Higher values result in more distortion. | ||
*/ | ||
void setDrive(SampleType newDrive) noexcept; | ||
|
||
//============================================================================== | ||
template <typename ProcessContext> | ||
void process(const ProcessContext &context) noexcept { | ||
const auto &inputBlock = context.getInputBlock(); | ||
auto &outputBlock = context.getOutputBlock(); | ||
const auto numChannels = outputBlock.getNumChannels(); | ||
const auto numSamples = outputBlock.getNumSamples(); | ||
|
||
jassert(inputBlock.getNumChannels() <= getNumChannels()); | ||
jassert(inputBlock.getNumChannels() == numChannels); | ||
jassert(inputBlock.getNumSamples() == numSamples); | ||
|
||
if (!enabled || context.isBypassed) { | ||
outputBlock.copyFrom(inputBlock); | ||
return; | ||
} | ||
|
||
for (size_t n = 0; n < numSamples; ++n) { | ||
updateSmoothers(); | ||
|
||
for (size_t ch = 0; ch < numChannels; ++ch) | ||
outputBlock.getChannelPointer(ch)[n] = | ||
processSample(inputBlock.getChannelPointer(ch)[n], ch); | ||
} | ||
} | ||
|
||
protected: | ||
//============================================================================== | ||
SampleType processSample(SampleType inputValue, size_t channelToUse) noexcept; | ||
void updateSmoothers() noexcept; | ||
|
||
private: | ||
//============================================================================== | ||
void setSampleRate(SampleType newValue) noexcept; | ||
void setNumChannels(size_t newValue) { state.resize(newValue); } | ||
void updateCutoffFreq() noexcept { | ||
cutoffTransformSmoother.setTargetValue( | ||
std::exp(cutoffFreqHz * cutoffFreqScaler)); | ||
} | ||
void updateResonance() noexcept { | ||
scaledResonanceSmoother.setTargetValue( | ||
jmap(resonance, SampleType(0.1), SampleType(1.0))); | ||
} | ||
|
||
//============================================================================== | ||
SampleType drive, drive2, gain, gain2, comp; | ||
|
||
static constexpr size_t numStates = 5; | ||
std::vector<std::array<SampleType, numStates>> state; | ||
std::array<SampleType, numStates> A; | ||
|
||
SmoothedValue<SampleType> cutoffTransformSmoother, scaledResonanceSmoother; | ||
SampleType cutoffTransformValue, scaledResonanceValue; | ||
|
||
LookupTableTransform<SampleType> saturationLUT{ | ||
[](SampleType x) { return std::tanh(x); }, SampleType(-5), SampleType(5), | ||
128}; | ||
|
||
SampleType cutoffFreqHz{SampleType(200)}; | ||
SampleType resonance; | ||
|
||
SampleType cutoffFreqScaler; | ||
|
||
Mode mode; | ||
bool enabled = true; | ||
}; | ||
|
||
} // namespace blackbird::dsp |
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