Browse Source

DSP module: Various fixes and features including new LadderFilter

tags/2021-05-28
jules 8 years ago
parent
commit
8bcb06ce6c
9 changed files with 461 additions and 6 deletions
  1. +9
    -1
      modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h
  2. +3
    -0
      modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp
  3. +1
    -1
      modules/juce_audio_basics/mpe/juce_MPESynthesiser.h
  4. +1
    -0
      modules/juce_dsp/juce_dsp.cpp
  5. +2
    -0
      modules/juce_dsp/juce_dsp.h
  6. +171
    -0
      modules/juce_dsp/processors/juce_LadderFilter.cpp
  7. +142
    -0
      modules/juce_dsp/processors/juce_LadderFilter.h
  8. +17
    -4
      modules/juce_dsp/processors/juce_Oscillator.h
  9. +115
    -0
      modules/juce_dsp/processors/juce_Reverb.h

+ 9
- 1
modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h View File

@@ -61,9 +61,17 @@ public:
//============================================================================== //==============================================================================
/** Set a new target value. /** Set a new target value.
@param newValue 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) if (target != newValue)
{ {
target = newValue; target = newValue;


+ 3
- 0
modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp View File

@@ -25,6 +25,9 @@ namespace juce
MPESynthesiser::MPESynthesiser() MPESynthesiser::MPESynthesiser()
{ {
MPEZoneLayout zoneLayout;
zoneLayout.addZone (MPEZone (1, 15));
setZoneLayout (zoneLayout);
} }
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument)


+ 1
- 1
modules/juce_audio_basics/mpe/juce_MPESynthesiser.h View File

@@ -301,7 +301,7 @@ protected:
private: private:
//============================================================================== //==============================================================================
bool shouldStealVoices;
bool shouldStealVoices = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
}; };


+ 1
- 0
modules/juce_dsp/juce_dsp.cpp View File

@@ -51,6 +51,7 @@
#include "processors/juce_FIRFilter.cpp" #include "processors/juce_FIRFilter.cpp"
#include "processors/juce_IIRFilter.cpp" #include "processors/juce_IIRFilter.cpp"
#include "processors/juce_LadderFilter.cpp"
#include "processors/juce_Oversampling.cpp" #include "processors/juce_Oversampling.cpp"
#include "maths/juce_SpecialFunctions.cpp" #include "maths/juce_SpecialFunctions.cpp"
#include "maths/juce_Matrix.cpp" #include "maths/juce_Matrix.cpp"


+ 2
- 0
modules/juce_dsp/juce_dsp.h View File

@@ -255,8 +255,10 @@ namespace juce
#include "processors/juce_IIRFilter.h" #include "processors/juce_IIRFilter.h"
#include "processors/juce_FIRFilter.h" #include "processors/juce_FIRFilter.h"
#include "processors/juce_Oscillator.h" #include "processors/juce_Oscillator.h"
#include "processors/juce_LadderFilter.h"
#include "processors/juce_StateVariableFilter.h" #include "processors/juce_StateVariableFilter.h"
#include "processors/juce_Oversampling.h" #include "processors/juce_Oversampling.h"
#include "processors/juce_Reverb.h"
#include "frequency/juce_FFT.h" #include "frequency/juce_FFT.h"
#include "frequency/juce_Convolution.h" #include "frequency/juce_Convolution.h"
#include "frequency/juce_Windowing.h" #include "frequency/juce_Windowing.h"


+ 171
- 0
modules/juce_dsp/processors/juce_LadderFilter.cpp View 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
- 0
modules/juce_dsp/processors/juce_LadderFilter.h View 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

+ 17
- 4
modules/juce_dsp/processors/juce_Oscillator.h View File

@@ -41,27 +41,40 @@ public:
*/ */
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type; 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). /** Creates an oscillator with a periodic input function (-pi..pi).
If lookup table is not zero, then the function will be approximated If lookup table is not zero, then the function will be approximated
with a lookup table. with a lookup table.
*/ */
Oscillator (const std::function<NumericType (NumericType)>& function, size_t lookupTableNumPoints = 0) 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) 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; lookupTable = table;
generator = [table] (NumericType x) { return (*table) (x); }; generator = [table] (NumericType x) { return (*table) (x); };
} }
else
{
generator = function;
}
} }
//============================================================================== //==============================================================================
/** Sets the frequency of the oscillator. */ /** 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. */ /** Returns the current frequency of the oscillator. */
NumericType getFrequency() const noexcept { return frequency.getTargetValue(); } NumericType getFrequency() const noexcept { return frequency.getTargetValue(); }


+ 115
- 0
modules/juce_dsp/processors/juce_Reverb.h View 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…
Cancel
Save