diff --git a/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h b/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h index 3dc342c418..be8fc64c8f 100644 --- a/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h +++ b/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h @@ -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; diff --git a/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp b/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp index 94763f1bc7..3c0a20fd28 100644 --- a/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp +++ b/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp @@ -25,6 +25,9 @@ namespace juce MPESynthesiser::MPESynthesiser() { + MPEZoneLayout zoneLayout; + zoneLayout.addZone (MPEZone (1, 15)); + setZoneLayout (zoneLayout); } MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) diff --git a/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h b/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h index 7c266a7b76..1bd8569540 100644 --- a/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h +++ b/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h @@ -301,7 +301,7 @@ protected: private: //============================================================================== - bool shouldStealVoices; + bool shouldStealVoices = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) }; diff --git a/modules/juce_dsp/juce_dsp.cpp b/modules/juce_dsp/juce_dsp.cpp index 1545ee54c5..f402fc3b4b 100644 --- a/modules/juce_dsp/juce_dsp.cpp +++ b/modules/juce_dsp/juce_dsp.cpp @@ -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" diff --git a/modules/juce_dsp/juce_dsp.h b/modules/juce_dsp/juce_dsp.h index 4fc1f92b51..c0b5d737ab 100644 --- a/modules/juce_dsp/juce_dsp.h +++ b/modules/juce_dsp/juce_dsp.h @@ -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" diff --git a/modules/juce_dsp/processors/juce_LadderFilter.cpp b/modules/juce_dsp/processors/juce_LadderFilter.cpp new file mode 100644 index 0000000000..36022e04c3 --- /dev/null +++ b/modules/juce_dsp/processors/juce_LadderFilter.cpp @@ -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 +LadderFilter::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 +void LadderFilter::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 +void LadderFilter::prepare (const juce::dsp::ProcessSpec& spec) +{ + setSampleRate (Type (spec.sampleRate)); + setNumChannels (spec.numChannels); + reset(); +} + +//============================================================================== +template +void LadderFilter::reset() noexcept +{ + for (auto& s : state) + s.fill (Type (0)); + + cutoffTransformSmoother.setValue (cutoffTransformSmoother.getTargetValue(), true); + scaledResonanceSmoother.setValue (scaledResonanceSmoother.getTargetValue(), true); +} + +//============================================================================== +template +void LadderFilter::setCutoffFrequencyHz (Type newValue) noexcept +{ + jassert (newValue > Type (0)); + cutoffFreqHz = newValue; + updateCutoffFreq(); +} + +//============================================================================== +template +void LadderFilter::setResonance (Type newValue) noexcept +{ + jassert (newValue >= Type (0) && newValue <= Type (1)); + resonance = newValue; + updateResonance(); +} + +//============================================================================== +template +void LadderFilter::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 +Type LadderFilter::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 +void LadderFilter::updateSmoothers() noexcept +{ + cutoffTransformValue = cutoffTransformSmoother.getNextValue(); + scaledResonanceValue = scaledResonanceSmoother.getNextValue(); +} + +//============================================================================== +template +void LadderFilter::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; +template class LadderFilter; + +} // namespace dsp +} // namespace juce diff --git a/modules/juce_dsp/processors/juce_LadderFilter.h b/modules/juce_dsp/processors/juce_LadderFilter.h new file mode 100644 index 0000000000..b4f7191cd8 --- /dev/null +++ b/modules/juce_dsp/processors/juce_LadderFilter.h @@ -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 +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 + 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> state; + std::array A; + + LinearSmoothedValue cutoffTransformSmoother; + LinearSmoothedValue scaledResonanceSmoother; + Type cutoffTransformValue; + Type scaledResonanceValue; + + LookupTableTransform 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 diff --git a/modules/juce_dsp/processors/juce_Oscillator.h b/modules/juce_dsp/processors/juce_Oscillator.h index 03f64f4ba8..97751510eb 100644 --- a/modules/juce_dsp/processors/juce_Oscillator.h +++ b/modules/juce_dsp/processors/juce_Oscillator.h @@ -41,27 +41,40 @@ public: */ using NumericType = typename SampleTypeHelpers::ElementType::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& function, size_t lookupTableNumPoints = 0) - : generator (function), frequency (440.0f) + { + initialise (function, lookupTableNumPoints); + } + + /** Initialises the oscillator with a waveform. */ + void initialise (const std::function& function, size_t lookupTableNumPoints = 0) { if (lookupTableNumPoints != 0) { - auto table = new LookupTableTransform (generator, static_cast (-1.0 * double_Pi), - static_cast (double_Pi), lookupTableNumPoints); + auto* table = new LookupTableTransform (function, static_cast (-1.0 * double_Pi), + static_cast (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(); } diff --git a/modules/juce_dsp/processors/juce_Reverb.h b/modules/juce_dsp/processors/juce_Reverb.h new file mode 100644 index 0000000000..507a64e1ba --- /dev/null +++ b/modules/juce_dsp/processors/juce_Reverb.h @@ -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 + 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