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