| @@ -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" | |||
| @@ -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 | |||
| @@ -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(); } | |||
| @@ -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 | |||