mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
DSP module: Various fixes and features including new LadderFilter
This commit is contained in:
parent
fedbb67452
commit
8bcb06ce6c
9 changed files with 461 additions and 6 deletions
|
|
@ -61,9 +61,17 @@ public:
|
|||
//==============================================================================
|
||||
/** Set a new target value.
|
||||
@param newValue New target value
|
||||
@force if true, the value will be set immediately, bypassing the ramp
|
||||
*/
|
||||
void setValue (FloatType newValue) noexcept
|
||||
void setValue (FloatType newValue, bool force = false) noexcept
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
target = currentValue = newValue;
|
||||
countdown = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target != newValue)
|
||||
{
|
||||
target = newValue;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ namespace juce
|
|||
|
||||
MPESynthesiser::MPESynthesiser()
|
||||
{
|
||||
MPEZoneLayout zoneLayout;
|
||||
zoneLayout.addZone (MPEZone (1, 15));
|
||||
setZoneLayout (zoneLayout);
|
||||
}
|
||||
|
||||
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument)
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ protected:
|
|||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool shouldStealVoices;
|
||||
bool shouldStealVoices = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
|
||||
#include "processors/juce_FIRFilter.cpp"
|
||||
#include "processors/juce_IIRFilter.cpp"
|
||||
#include "processors/juce_LadderFilter.cpp"
|
||||
#include "processors/juce_Oversampling.cpp"
|
||||
#include "maths/juce_SpecialFunctions.cpp"
|
||||
#include "maths/juce_Matrix.cpp"
|
||||
|
|
|
|||
|
|
@ -255,8 +255,10 @@ namespace juce
|
|||
#include "processors/juce_IIRFilter.h"
|
||||
#include "processors/juce_FIRFilter.h"
|
||||
#include "processors/juce_Oscillator.h"
|
||||
#include "processors/juce_LadderFilter.h"
|
||||
#include "processors/juce_StateVariableFilter.h"
|
||||
#include "processors/juce_Oversampling.h"
|
||||
#include "processors/juce_Reverb.h"
|
||||
#include "frequency/juce_FFT.h"
|
||||
#include "frequency/juce_Convolution.h"
|
||||
#include "frequency/juce_Windowing.h"
|
||||
|
|
|
|||
171
modules/juce_dsp/processors/juce_LadderFilter.cpp
Normal file
171
modules/juce_dsp/processors/juce_LadderFilter.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
LadderFilter<Type>::LadderFilter()
|
||||
: state (2)
|
||||
{
|
||||
setSampleRate (Type (1000)); // intentionally setting unrealistic default
|
||||
// sample rate to catch missing initialisation bugs
|
||||
setResonance (Type (0));
|
||||
setDrive (Type (1.2));
|
||||
setMode (Mode::LPF12);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setMode (Mode newValue) noexcept
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case Mode::LPF12: A = { Type (0), Type (0), Type (1), Type (0), Type (0) }; comp = Type (0.5); break;
|
||||
case Mode::HPF12: A = { Type (1), Type (-2), Type (1), Type (0), Type (0) }; comp = Type (0); break;
|
||||
case Mode::LPF24: A = { Type (0), Type (0), Type (0), Type (0), Type (1) }; comp = Type (0.5); break;
|
||||
case Mode::HPF24: A = { Type (1), Type (-4), Type (6), Type (-4), Type (1) }; comp = Type (0); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
static constexpr auto outputGain = Type (1.2);
|
||||
|
||||
for (auto& a : A)
|
||||
a *= outputGain;
|
||||
|
||||
mode = newValue;
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::prepare (const juce::dsp::ProcessSpec& spec)
|
||||
{
|
||||
setSampleRate (Type (spec.sampleRate));
|
||||
setNumChannels (spec.numChannels);
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::reset() noexcept
|
||||
{
|
||||
for (auto& s : state)
|
||||
s.fill (Type (0));
|
||||
|
||||
cutoffTransformSmoother.setValue (cutoffTransformSmoother.getTargetValue(), true);
|
||||
scaledResonanceSmoother.setValue (scaledResonanceSmoother.getTargetValue(), true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setCutoffFrequencyHz (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue > Type (0));
|
||||
cutoffFreqHz = newValue;
|
||||
updateCutoffFreq();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setResonance (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue >= Type (0) && newValue <= Type (1));
|
||||
resonance = newValue;
|
||||
updateResonance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setDrive (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue >= Type (1));
|
||||
|
||||
drive = newValue;
|
||||
gain = std::pow (drive, Type (-2.642)) * Type (0.6103) + Type (0.3903);
|
||||
drive2 = drive * Type (0.04) + Type (0.96);
|
||||
gain2 = std::pow (drive2, Type (-2.642)) * Type (0.6103) + Type (0.3903);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
Type LadderFilter<Type>::processSample (Type inputValue, size_t channelToUse) noexcept
|
||||
{
|
||||
auto& s = state[channelToUse];
|
||||
|
||||
const auto a1 = cutoffTransformValue;
|
||||
const auto g = a1 * Type (-1) + Type (1);
|
||||
const auto b0 = g * Type (0.76923076923);
|
||||
const auto b1 = g * Type (0.23076923076);
|
||||
|
||||
const auto dx = gain * saturationLUT (drive * inputValue);
|
||||
const auto a = dx + scaledResonanceValue * Type (-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 Type>
|
||||
void LadderFilter<Type>::updateSmoothers() noexcept
|
||||
{
|
||||
cutoffTransformValue = cutoffTransformSmoother.getNextValue();
|
||||
scaledResonanceValue = scaledResonanceSmoother.getNextValue();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setSampleRate (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue > Type (0));
|
||||
cutoffFreqScaler = Type (-2 * juce::double_Pi) / newValue;
|
||||
|
||||
static constexpr auto smootherRampTimeSec = Type (5e-2);
|
||||
cutoffTransformSmoother.reset (newValue, smootherRampTimeSec);
|
||||
scaledResonanceSmoother.reset (newValue, smootherRampTimeSec);
|
||||
|
||||
updateCutoffFreq();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class LadderFilter<float>;
|
||||
template class LadderFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
142
modules/juce_dsp/processors/juce_LadderFilter.h
Normal file
142
modules/juce_dsp/processors/juce_LadderFilter.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Multi-mode filter based on the Moog ladder filter.
|
||||
*/
|
||||
template <typename Type>
|
||||
class LadderFilter
|
||||
{
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
LPF12, // low-pass 12 dB/octave
|
||||
HPF12, // high-pass 12 dB/octave
|
||||
LPF24, // low-pass 24 dB/octave
|
||||
HPF24 // high-pass 24 dB/octave
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** 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 newValue) noexcept { enabled = newValue; }
|
||||
|
||||
/** Sets filter mode. */
|
||||
void setMode (Mode newValue) noexcept;
|
||||
|
||||
/** Initialises the filter. */
|
||||
void prepare (const juce::dsp::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 newValue cutoff frequency in Hz */
|
||||
void setCutoffFrequencyHz (Type newValue) noexcept;
|
||||
|
||||
/** Sets the resonance of the filter.
|
||||
@param newValue a value between 0 and 1; higher values increase the resonance and can result in self oscillation! */
|
||||
void setResonance (Type newValue) noexcept;
|
||||
|
||||
/** Sets the amound of saturation in the filter.
|
||||
@param newValue saturation amount; it can be any number greater than or equal to one. Higher values result in more distortion.*/
|
||||
void setDrive (Type newValue) 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.copy (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:
|
||||
//==============================================================================
|
||||
Type processSample (Type inputValue, size_t channelToUse) noexcept;
|
||||
void updateSmoothers() noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Type drive, drive2, gain, gain2, comp;
|
||||
|
||||
static constexpr size_t numStates = 5;
|
||||
std::vector<std::array<Type, numStates>> state;
|
||||
std::array<Type, numStates> A;
|
||||
|
||||
LinearSmoothedValue<Type> cutoffTransformSmoother;
|
||||
LinearSmoothedValue<Type> scaledResonanceSmoother;
|
||||
Type cutoffTransformValue;
|
||||
Type scaledResonanceValue;
|
||||
|
||||
LookupTableTransform<Type> saturationLUT { [] (Type x) { return std::tanh (x); }, Type (-5), Type (5), 128 };
|
||||
|
||||
Type cutoffFreqHz { Type (200) };
|
||||
Type resonance;
|
||||
|
||||
Type cutoffFreqScaler;
|
||||
|
||||
Mode mode;
|
||||
bool enabled = true;
|
||||
|
||||
//==============================================================================
|
||||
void setSampleRate (Type newValue) noexcept;
|
||||
void setNumChannels (size_t newValue) { state.resize (newValue); }
|
||||
void updateCutoffFreq() noexcept { cutoffTransformSmoother.setValue (std::exp (cutoffFreqHz * cutoffFreqScaler)); }
|
||||
void updateResonance() noexcept { scaledResonanceSmoother.setValue (jmap (resonance, Type (0.1), Type (1.0))); }
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
|
@ -41,27 +41,40 @@ public:
|
|||
*/
|
||||
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type;
|
||||
|
||||
/** Creates an uninitialised oscillator. Call initialise before first use. */
|
||||
Oscillator()
|
||||
{}
|
||||
|
||||
/** Creates an oscillator with a periodic input function (-pi..pi).
|
||||
|
||||
If lookup table is not zero, then the function will be approximated
|
||||
with a lookup table.
|
||||
*/
|
||||
Oscillator (const std::function<NumericType (NumericType)>& function, size_t lookupTableNumPoints = 0)
|
||||
: generator (function), frequency (440.0f)
|
||||
{
|
||||
initialise (function, lookupTableNumPoints);
|
||||
}
|
||||
|
||||
/** Initialises the oscillator with a waveform. */
|
||||
void initialise (const std::function<NumericType (NumericType)>& function, size_t lookupTableNumPoints = 0)
|
||||
{
|
||||
if (lookupTableNumPoints != 0)
|
||||
{
|
||||
auto table = new LookupTableTransform<NumericType> (generator, static_cast <NumericType> (-1.0 * double_Pi),
|
||||
static_cast<NumericType> (double_Pi), lookupTableNumPoints);
|
||||
auto* table = new LookupTableTransform<NumericType> (function, static_cast <NumericType> (-1.0 * double_Pi),
|
||||
static_cast<NumericType> (double_Pi), lookupTableNumPoints);
|
||||
|
||||
lookupTable = table;
|
||||
generator = [table] (NumericType x) { return (*table) (x); };
|
||||
}
|
||||
else
|
||||
{
|
||||
generator = function;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the frequency of the oscillator. */
|
||||
void setFrequency (NumericType newGain) noexcept { frequency.setValue (newGain); }
|
||||
void setFrequency (NumericType newGain, bool force = false) noexcept { frequency.setValue (newGain, force); }
|
||||
|
||||
/** Returns the current frequency of the oscillator. */
|
||||
NumericType getFrequency() const noexcept { return frequency.getTargetValue(); }
|
||||
|
|
|
|||
115
modules/juce_dsp/processors/juce_Reverb.h
Normal file
115
modules/juce_dsp/processors/juce_Reverb.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Processor wrapper around juce::Reverb for easy integration into ProcessorChain.
|
||||
*/
|
||||
class Reverb
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised Reverb processor. Call prepare() before first use. */
|
||||
Reverb()
|
||||
{}
|
||||
|
||||
//==============================================================================
|
||||
using Parameters = juce::Reverb::Parameters;
|
||||
|
||||
/** Returns the reverb's current parameters. */
|
||||
const Parameters& getParameters() const noexcept { return reverb.getParameters(); }
|
||||
|
||||
/** Applies a new set of parameters to the reverb.
|
||||
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
|
||||
the process method, you may get artifacts.
|
||||
*/
|
||||
void setParameters (const Parameters& newParams) { reverb.setParameters (newParams); }
|
||||
|
||||
/** Returns true if the reverb is enabled. */
|
||||
bool isEnabled() const noexcept { return enabled; }
|
||||
|
||||
/** Enables/disables the reverb. */
|
||||
void setEnabled (bool newValue) noexcept { enabled = newValue; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the reverb. */
|
||||
void prepare (const juce::dsp::ProcessSpec& spec)
|
||||
{
|
||||
reverb.setSampleRate (spec.sampleRate);
|
||||
}
|
||||
|
||||
/** Resets the reverb's internal state. */
|
||||
void reset() noexcept
|
||||
{
|
||||
reverb.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Applies the reverb to a mono or stereo buffer. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
const auto& inputBlock = context.getInputBlock();
|
||||
auto& outputBlock = context.getOutputBlock();
|
||||
const auto numInChannels = inputBlock.getNumChannels();
|
||||
const auto numOutChannels = outputBlock.getNumChannels();
|
||||
const auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
outputBlock.copy (inputBlock);
|
||||
|
||||
if (! enabled || context.isBypassed)
|
||||
return;
|
||||
|
||||
if (numInChannels == 1 && numOutChannels == 1)
|
||||
{
|
||||
reverb.processMono (outputBlock.getChannelPointer (0), (int) numSamples);
|
||||
}
|
||||
else if (numInChannels == 2 && numOutChannels == 2)
|
||||
{
|
||||
reverb.processStereo (outputBlock.getChannelPointer (0),
|
||||
outputBlock.getChannelPointer (1),
|
||||
(int) numSamples);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // invalid channel configuration
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
juce::Reverb reverb;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
Loading…
Add table
Add a link
Reference in a new issue